KiCad PCB EDA Suite
Loading...
Searching...
No Matches
shape_utils.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, you may find one here:
18 * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
19 * or you may search the http://www.gnu.org website for the version 2 license,
20 * or you may write to the Free Software Foundation, Inc.,
21 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
22 */
23
25
26#include <geometry/circle.h>
27#include <geometry/seg.h>
28#include <geometry/half_line.h>
29#include <geometry/line.h>
32#include <geometry/shape_rect.h>
33
34
36{
37 if( LexicographicalCompare( aSeg.A, aSeg.B ) <= 0 )
38 {
39 return aSeg;
40 }
41 return aSeg.Reversed();
42}
43
44
45const VECTOR2I& KIGEOM::GetOtherEnd( const SEG& aSeg, const VECTOR2I& aPoint )
46{
47 return ( aSeg.A == aPoint ) ? aSeg.B : aSeg.A;
48}
49
50
51OPT_VECTOR2I KIGEOM::GetSharedEndpoint( const SEG& aSegA, const SEG& aSegB )
52{
53 if( aSegA.A == aSegB.A || aSegA.A == aSegB.B )
54 {
55 return aSegA.A;
56 }
57 else if( aSegA.B == aSegB.A || aSegA.B == aSegB.B )
58 {
59 return aSegA.B;
60 }
61
62 return std::nullopt;
63}
64
65
66std::array<SEG, 4> KIGEOM::BoxToSegs( const BOX2I& aBox )
67{
68 const std::array<VECTOR2I, 4> corners = {
69 VECTOR2I{ aBox.GetLeft(), aBox.GetTop() },
70 VECTOR2I{ aBox.GetRight(), aBox.GetTop() },
71 VECTOR2I{ aBox.GetRight(), aBox.GetBottom() },
72 VECTOR2I{ aBox.GetLeft(), aBox.GetBottom() },
73 };
74
75 return {
76 SEG{ corners[0], corners[1] },
77 SEG{ corners[1], corners[2] },
78 SEG{ corners[2], corners[3] },
79 SEG{ corners[3], corners[0] },
80 };
81}
82
83
84void KIGEOM::CollectBoxCorners( const BOX2I& aBox, std::vector<VECTOR2I>& aCorners )
85{
86 aCorners.push_back( { aBox.GetLeft(), aBox.GetTop() } );
87 aCorners.push_back( { aBox.GetRight(), aBox.GetTop() } );
88 aCorners.push_back( { aBox.GetRight(), aBox.GetBottom() } );
89 aCorners.push_back( { aBox.GetLeft(), aBox.GetBottom() } );
90}
91
92
94{
96
97 result.Append( VECTOR2I{ aBox.GetLeft(), aBox.GetTop() } );
98 result.Append( VECTOR2I{ aBox.GetRight(), aBox.GetTop() } );
99 result.Append( VECTOR2I{ aBox.GetRight(), aBox.GetBottom() } );
100 result.Append( VECTOR2I{ aBox.GetLeft(), aBox.GetBottom() } );
101 result.SetClosed( true );
102
103 return result;
104}
105
106
107std::vector<SEG> KIGEOM::GetSegsInDirection( const BOX2I& aBox, DIRECTION_45::Directions aDir )
108{
109 // clang-format off
110 switch( aDir )
111 {
113 return { SEG{ { aBox.GetLeft(), aBox.GetTop() },
114 { aBox.GetRight(), aBox.GetTop() } } };
116 return { SEG{ { aBox.GetRight(), aBox.GetTop() },
117 { aBox.GetRight(), aBox.GetBottom() } } };
119 return { SEG{ { aBox.GetLeft(), aBox.GetBottom() },
120 { aBox.GetRight(), aBox.GetBottom() } } };
122 return { SEG{ { aBox.GetLeft(), aBox.GetTop() },
123 { aBox.GetLeft(), aBox.GetBottom() } } };
125 return { SEG{ { aBox.GetLeft(), aBox.GetTop() },
126 { aBox.GetRight(), aBox.GetTop() } },
127 SEG{ { aBox.GetRight(), aBox.GetTop() },
128 { aBox.GetRight(), aBox.GetBottom() } } };
130 return { SEG{ { aBox.GetLeft(), aBox.GetBottom() },
131 { aBox.GetRight(), aBox.GetBottom() } },
132 SEG{ { aBox.GetRight(), aBox.GetTop() },
133 { aBox.GetRight(), aBox.GetBottom() } } };
135 return { SEG{ { aBox.GetLeft(), aBox.GetBottom() },
136 { aBox.GetRight(), aBox.GetBottom() } },
137 SEG{ { aBox.GetLeft(), aBox.GetTop() },
138 { aBox.GetLeft(), aBox.GetBottom() } } };
140 return { SEG{ { aBox.GetLeft(), aBox.GetTop() },
141 { aBox.GetRight(), aBox.GetTop() } },
142 SEG{ { aBox.GetLeft(), aBox.GetTop() },
143 { aBox.GetLeft(), aBox.GetBottom() } } };
146 }
147 // clang-format on
148
149 wxASSERT( false );
150 return {};
151};
152
153
154std::optional<SEG> KIGEOM::ClipHalfLineToBox( const HALF_LINE& aRay, const BOX2I& aBox )
155{
156 // Do the naive implementation - if this really is done in a tight loop,
157 // the Cohen-Sutherland implementation in ClipLine could be faster, but
158 // needs to be adapted to work with half-lines.
159 const std::array<SEG, 4> boxSegs = KIGEOM::BoxToSegs( aBox );
160
161 std::optional<VECTOR2I> ptA, ptB;
162
163 for( const SEG& boxSeg : boxSegs )
164 {
165 OPT_VECTOR2I intersection = aRay.Intersect( boxSeg );
166
167 if( !intersection )
168 {
169 continue;
170 }
171
172 // Init the first point or eat it if it's the same
173 if( !ptA || *intersection == *ptA )
174 {
175 ptA = *intersection;
176 }
177 else
178 {
179 ptB = *intersection;
180 }
181 }
182
183 // If we have exactly two intersections, the ray crossed twice
184 // so take the segment between the two points
185 if( ptA && ptB )
186 {
187 return SEG( *ptA, *ptB );
188 }
189
190 // It only crosses once, so the start is in the box. Take the segment from
191 // the start point to the intersection
192 if( ptA && *ptA != aRay.GetStart() )
193 {
194 return SEG( aRay.GetStart(), *ptA );
195 }
196
197 // It didn't cross at all
198 return std::nullopt;
199}
200
201
202std::optional<SEG> KIGEOM::ClipLineToBox( const LINE& aLine, const BOX2I& aBox )
203{
204 // As above, maybe can be optimised?
205 const std::array<SEG, 4> boxSegs = KIGEOM::BoxToSegs( aBox );
206
207 std::optional<VECTOR2I> ptA, ptB;
208
209 for( const SEG& boxSeg : boxSegs )
210 {
211 OPT_VECTOR2I intersection = aLine.Intersect( boxSeg );
212
213 // Reject intersections that are not on the actual box boundary
214 if( intersection && boxSeg.Contains( *intersection ) )
215 {
216 // Init the first point or eat it if it's the same
217 if( !ptA || *intersection == *ptA )
218 {
219 ptA = *intersection;
220 }
221 else
222 {
223 ptB = *intersection;
224 }
225 }
226 }
227
228 // If we have exactly two intersections, we have a segment
229 // (zero is no intersection, and one is a just crossing a corner exactly)
230 if( ptA && ptB )
231 {
232 return SEG( *ptA, *ptB );
233 }
234
235 return std::nullopt;
236}
237
238
240{
241 switch( aDir )
242 {
243 case DIRECTION_45::NW:
244 return SHAPE_ARC{
245 aCenter,
246 aCenter + VECTOR2I( -aRadius, 0 ),
247 ANGLE_90,
248 };
249 case DIRECTION_45::NE:
250 return SHAPE_ARC{
251 aCenter,
252 aCenter + VECTOR2I( 0, -aRadius ),
253 ANGLE_90,
254 };
255 case DIRECTION_45::SW:
256 return SHAPE_ARC{
257 aCenter,
258 aCenter + VECTOR2I( 0, aRadius ),
259 ANGLE_90,
260 };
261 case DIRECTION_45::SE:
262 return SHAPE_ARC{
263 aCenter,
264 aCenter + VECTOR2I( aRadius, 0 ),
265 ANGLE_90,
266 };
267 default: wxFAIL_MSG( "Invalid direction" ); return SHAPE_ARC();
268 }
269}
270
271
272SHAPE_ARC KIGEOM::MakeArcCw180( const VECTOR2I& aCenter, int aRadius,
274{
275 switch( aDir )
276 {
277 case DIRECTION_45::N:
278 return SHAPE_ARC{
279 aCenter,
280 aCenter + VECTOR2I( -aRadius, 0 ),
281 ANGLE_180,
282 };
283 case DIRECTION_45::E:
284 return SHAPE_ARC{
285 aCenter,
286 aCenter + VECTOR2I( 0, -aRadius ),
287 ANGLE_180,
288 };
289 case DIRECTION_45::S:
290 return SHAPE_ARC{
291 aCenter,
292 aCenter + VECTOR2I( aRadius, 0 ),
293 ANGLE_180,
294 };
295 case DIRECTION_45::W:
296 return SHAPE_ARC{
297 aCenter,
298 aCenter + VECTOR2I( 0, aRadius ),
299 ANGLE_180,
300 };
301 default: wxFAIL_MSG( "Invalid direction" );
302 }
303
304 return SHAPE_ARC();
305}
306
307
309{
310 const VECTOR2I nw = aRect.GetPosition();
311 switch( aDir )
312 {
313 // clang-format off
314 case DIRECTION_45::N:
315 return nw + VECTOR2I( aRect.GetWidth() / 2, -aOutset );
316 case DIRECTION_45::E:
317 return nw + VECTOR2I( aRect.GetWidth() + aOutset, aRect.GetHeight() / 2 );
318 case DIRECTION_45::S:
319 return nw + VECTOR2I( aRect.GetWidth() / 2, aRect.GetHeight() + aOutset );
320 case DIRECTION_45::W:
321 return nw + VECTOR2I( -aOutset, aRect.GetHeight() / 2 );
322 case DIRECTION_45::NW:
323 return nw + VECTOR2I( -aOutset, -aOutset );
324 case DIRECTION_45::NE:
325 return nw + VECTOR2I( aRect.GetWidth() + aOutset, -aOutset );
326 case DIRECTION_45::SW:
327 return nw + VECTOR2I( -aOutset, aRect.GetHeight() + aOutset );
328 case DIRECTION_45::SE:
329 return nw + VECTOR2I( aRect.GetWidth() + aOutset, aRect.GetHeight() + aOutset );
330 default:
331 wxFAIL_MSG( "Invalid direction" );
332 // clang-format on
333 }
334 return VECTOR2I();
335}
336
337
338std::vector<TYPED_POINT2I> KIGEOM::GetCircleKeyPoints( const CIRCLE& aCircle, bool aIncludeCenter )
339{
340 std::vector<TYPED_POINT2I> pts;
341
342 if( aIncludeCenter )
343 {
344 pts.emplace_back( VECTOR2I{ 0, 0 }, POINT_TYPE::PT_CENTER );
345 }
346
347 pts.emplace_back( VECTOR2I{ 0, aCircle.Radius }, POINT_TYPE::PT_QUADRANT );
348 pts.emplace_back( VECTOR2I{ aCircle.Radius, 0 }, POINT_TYPE::PT_QUADRANT );
349 pts.emplace_back( VECTOR2I{ 0, -aCircle.Radius }, POINT_TYPE::PT_QUADRANT );
350 pts.emplace_back( VECTOR2I{ -aCircle.Radius, 0 }, POINT_TYPE::PT_QUADRANT );
351
352 // Shift the points to the circle center
353 for( TYPED_POINT2I& pt : pts )
354 {
355 pt.m_point += aCircle.Center;
356 }
357
358 return pts;
359}
360
361
363{
364 SHAPE_LINE_CHAIN raOutline;
365
366 const auto handleSegment = [&]( const SEG& aSeg )
367 {
368 const VECTOR2I p0( aSeg.A.x, aSeg.B.y );
369 const VECTOR2I p1( aSeg.B.x, aSeg.A.y );
370
371 raOutline.Append( aSeg.A );
372 if( !aPoly.PointInside( p0 ) )
373 raOutline.Append( p0 );
374 else
375 raOutline.Append( p1 );
376 };
377
378 for( int i = 0; i < aPoly.SegmentCount(); i++ )
379 {
380 handleSegment( aPoly.CSegment( i ) );
381 }
382
383 // Manually handle the last segment if not closed
384 if( !aPoly.IsClosed() && aPoly.PointCount() >= 2 )
385 {
386 handleSegment( SEG( aPoly.CLastPoint(), aPoly.CPoint( 0 ) ) );
387 }
388
389 raOutline.SetClosed( true );
390 raOutline.Simplify();
391
392 return raOutline;
393}
394
395
397{
398 if( aHole.PointCount() < 3 || aHole.Area() == 0 )
399 {
400 return false;
401 }
402
403 aOutline.AddHole( std::move( aHole ) );
404 return true;
405}
406
407
408std::vector<VECTOR2I> KIGEOM::MakeRegularPolygonPoints( const VECTOR2I& aCenter, size_t aN,
409 const VECTOR2I& aPt0 )
410{
411 VECTOR2D pt0FromC = aPt0 - aCenter;
412 std::vector<VECTOR2I> pts;
413
414 for( size_t i = 0; i < aN; i++ )
415 {
416 VECTOR2D pt = GetRotated( pt0FromC, ( FULL_CIRCLE / double( aN ) ) * i );
417 pts.push_back( KiROUND( pt + aCenter ) );
418 }
419
420 return pts;
421}
422
423
424std::vector<VECTOR2I> KIGEOM::MakeRegularPolygonPoints( const VECTOR2I& aCenter, size_t aN,
425 int aRadius, bool aAcrossCorners,
426 EDA_ANGLE aAngle )
427{
428 if( !aAcrossCorners )
429 {
430 // if across flats, increase the radius
431 aRadius = aRadius / ( FULL_CIRCLE / ( aN * 2 ) ).Cos();
432 }
433
434 const VECTOR2I pt0 = aCenter + GetRotated( VECTOR2I{ aRadius, 0 }, aAngle );
435 return KIGEOM::MakeRegularPolygonPoints( aCenter, aN, pt0 );
436}
437
438
439std::vector<SEG> KIGEOM::MakeCrossSegments( const VECTOR2I& aCenter, const VECTOR2I& aSize,
440 EDA_ANGLE aAngle )
441{
442 std::vector<SEG> segs;
443
444 VECTOR2I pt0 = aCenter - GetRotated( VECTOR2I{ aSize.x / 2, 0 }, aAngle );
445 segs.emplace_back( pt0, aCenter - ( pt0 - aCenter ) );
446
447 pt0 = aCenter - GetRotated( VECTOR2I{ 0, aSize.y / 2 }, aAngle );
448 segs.emplace_back( pt0, aCenter - ( pt0 - aCenter ) );
449
450 return segs;
451}
BOX2< VECTOR2I > BOX2I
Definition box2.h:922
constexpr BOX2I KiROUND(const BOX2D &aBoxD)
Definition box2.h:990
constexpr coord_type GetLeft() const
Definition box2.h:228
constexpr coord_type GetRight() const
Definition box2.h:217
constexpr coord_type GetTop() const
Definition box2.h:229
constexpr coord_type GetBottom() const
Definition box2.h:222
Represent basic circle geometry with utility geometry functions.
Definition circle.h:33
VECTOR2I Center
Public to make access simpler.
Definition circle.h:130
int Radius
Public to make access simpler.
Definition circle.h:129
Directions
Available directions, there are 8 of them, as on a rectilinear map (north = up) + an extra undefined ...
Definition direction45.h:49
OPT_VECTOR2I Intersect(const SEG &aSeg) const
Definition half_line.cpp:57
const VECTOR2I & GetStart() const
Get the start point of the ray.
Definition half_line.h:57
Definition line.h:36
OPT_VECTOR2I Intersect(const SEG &aOther) const
Definition line.cpp:22
Definition seg.h:42
VECTOR2I A
Definition seg.h:49
VECTOR2I B
Definition seg.h:50
SEG Reversed() const
Returns the center point of the line.
Definition seg.h:373
bool PointInside(const VECTOR2I &aPt, int aAccuracy=0, bool aUseBBoxCache=false) const override
Check if point aP lies inside a closed shape.
Represent a polyline containing arcs as well as line segments: A chain of connected line and/or arc s...
bool IsClosed() const override
void SetClosed(bool aClosed)
Mark the line chain as closed (i.e.
int PointCount() const
Return the number of points (vertices) in this line chain.
void Simplify(int aTolerance=0)
Simplify the line chain by removing colinear adjacent segments and duplicate vertices.
void Append(int aX, int aY, bool aAllowDuplication=false)
Append a new point at the end of the line chain.
const VECTOR2I & CPoint(int aIndex) const
Return a reference to a given point in the line chain.
int SegmentCount() const
Return the number of segments in this line chain.
const VECTOR2I & CLastPoint() const
Return the last point in the line chain.
const SEG CSegment(int aIndex) const
Return a constant copy of the aIndex segment in the line chain.
Represent a set of closed polygons.
int AddHole(const SHAPE_LINE_CHAIN &aHole, int aOutline=-1)
Adds a new hole to the given outline (default: last) and returns its index.
int GetWidth() const override
Definition shape_rect.h:185
const VECTOR2I & GetPosition() const
Definition shape_rect.h:169
int GetHeight() const
Definition shape_rect.h:193
static constexpr EDA_ANGLE ANGLE_90
Definition eda_angle.h:413
static constexpr EDA_ANGLE FULL_CIRCLE
Definition eda_angle.h:409
static constexpr EDA_ANGLE ANGLE_180
Definition eda_angle.h:415
bool AddHoleIfValid(SHAPE_POLY_SET &aOutline, SHAPE_LINE_CHAIN &&aHole)
Adds a hole to a polygon if it is valid (i.e.
VECTOR2I GetPoint(const SHAPE_RECT &aRect, DIRECTION_45::Directions aDir, int aOutset=0)
Get the point on a rectangle that corresponds to a given direction.
std::vector< TYPED_POINT2I > GetCircleKeyPoints(const CIRCLE &aCircle, bool aIncludeCenter)
Get key points of an CIRCLE.
std::vector< VECTOR2I > MakeRegularPolygonPoints(const VECTOR2I &aCenter, size_t aN, const VECTOR2I &aPt0)
Get the corners of a regular polygon from the centre, one point and the number of sides.
OPT_VECTOR2I GetSharedEndpoint(const SEG &aSegA, const SEG &aSegB)
Get the shared endpoint of two segments, if it exists, or std::nullopt if the segments are not connec...
std::vector< SEG > GetSegsInDirection(const BOX2I &aBox, DIRECTION_45::Directions aDir)
Get the segments of a box that are in the given direction.
SEG NormalisedSeg(const SEG &aSeg)
Returns a SEG such that the start point is smaller or equal in x and y compared to the end point.
std::vector< SEG > MakeCrossSegments(const VECTOR2I &aCenter, const VECTOR2I &aSize, EDA_ANGLE aAngle)
Create the two segments for a cross.
std::array< SEG, 4 > BoxToSegs(const BOX2I &aBox)
Decompose a BOX2 into four segments.
std::optional< SEG > ClipHalfLineToBox(const HALF_LINE &aRay, const BOX2I &aBox)
Get the segment of a half-line that is inside a box, if any.
std::optional< SEG > ClipLineToBox(const LINE &aLine, const BOX2I &aBox)
Get the segment of a line that is inside a box, if any.
SHAPE_ARC MakeArcCw180(const VECTOR2I &aCenter, int aRadius, DIRECTION_45::Directions aDir)
Get a SHAPE_ARC representing a 180-degree arc in the clockwise direction with the midpoint in the giv...
SHAPE_LINE_CHAIN RectifyPolygon(const SHAPE_LINE_CHAIN &aPoly)
SHAPE_ARC MakeArcCw90(const VECTOR2I &aCenter, int aRadius, DIRECTION_45::Directions aDir)
Get a SHAPE_ARC representing a 90-degree arc in the clockwise direction with the midpoint in the give...
SHAPE_LINE_CHAIN BoxToLineChain(const BOX2I &aBox)
void CollectBoxCorners(const BOX2I &aBox, std::vector< VECTOR2I > &aCorners)
Add the 4 corners of a BOX2I to a vector.
const VECTOR2I & GetOtherEnd(const SEG &aSeg, const VECTOR2I &aPoint)
Get the end point of the segment that is not the given point.
@ PT_CENTER
The point is the center of something.
Definition point_types.h:46
@ PT_QUADRANT
The point is on a quadrant of a circle (N, E, S, W points).
Definition point_types.h:58
std::optional< VECTOR2I > OPT_VECTOR2I
Definition seg.h:39
Utility functions for working with shapes.
wxString result
Test unit parsing edge cases and error handling.
VECTOR2I GetRotated(const VECTOR2I &aVector, const EDA_ANGLE &aAngle)
Return a new VECTOR2I that is the result of rotating aVector by aAngle.
Definition trigo.h:77
VECTOR2< int32_t > VECTOR2I
Definition vector2d.h:695
VECTOR2< double > VECTOR2D
Definition vector2d.h:694
constexpr int LexicographicalCompare(const VECTOR2< T > &aA, const VECTOR2< T > &aB)
Definition vector2d.h:644