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, see <https://www.gnu.org/licenses/>.
18 */
19
21
22#include <geometry/circle.h>
23#include <geometry/seg.h>
24#include <geometry/half_line.h>
25#include <geometry/line.h>
28#include <geometry/shape_rect.h>
29
30
32{
33 if( LexicographicalCompare( aSeg.A, aSeg.B ) <= 0 )
34 {
35 return aSeg;
36 }
37 return aSeg.Reversed();
38}
39
40
41const VECTOR2I& KIGEOM::GetOtherEnd( const SEG& aSeg, const VECTOR2I& aPoint )
42{
43 return ( aSeg.A == aPoint ) ? aSeg.B : aSeg.A;
44}
45
46
47OPT_VECTOR2I KIGEOM::GetSharedEndpoint( const SEG& aSegA, const SEG& aSegB )
48{
49 if( aSegA.A == aSegB.A || aSegA.A == aSegB.B )
50 {
51 return aSegA.A;
52 }
53 else if( aSegA.B == aSegB.A || aSegA.B == aSegB.B )
54 {
55 return aSegA.B;
56 }
57
58 return std::nullopt;
59}
60
61
62std::array<SEG, 4> KIGEOM::BoxToSegs( const BOX2I& aBox )
63{
64 const std::array<VECTOR2I, 4> corners = {
65 VECTOR2I{ aBox.GetLeft(), aBox.GetTop() },
66 VECTOR2I{ aBox.GetRight(), aBox.GetTop() },
67 VECTOR2I{ aBox.GetRight(), aBox.GetBottom() },
68 VECTOR2I{ aBox.GetLeft(), aBox.GetBottom() },
69 };
70
71 return {
72 SEG{ corners[0], corners[1] },
73 SEG{ corners[1], corners[2] },
74 SEG{ corners[2], corners[3] },
75 SEG{ corners[3], corners[0] },
76 };
77}
78
79
80void KIGEOM::CollectBoxCorners( const BOX2I& aBox, std::vector<VECTOR2I>& aCorners )
81{
82 aCorners.push_back( { aBox.GetLeft(), aBox.GetTop() } );
83 aCorners.push_back( { aBox.GetRight(), aBox.GetTop() } );
84 aCorners.push_back( { aBox.GetRight(), aBox.GetBottom() } );
85 aCorners.push_back( { aBox.GetLeft(), aBox.GetBottom() } );
86}
87
88
90{
92
93 result.Append( VECTOR2I{ aBox.GetLeft(), aBox.GetTop() } );
94 result.Append( VECTOR2I{ aBox.GetRight(), aBox.GetTop() } );
95 result.Append( VECTOR2I{ aBox.GetRight(), aBox.GetBottom() } );
96 result.Append( VECTOR2I{ aBox.GetLeft(), aBox.GetBottom() } );
97 result.SetClosed( true );
98
99 return result;
100}
101
102
103std::vector<SEG> KIGEOM::GetSegsInDirection( const BOX2I& aBox, DIRECTION_45::Directions aDir )
104{
105 // clang-format off
106 switch( aDir )
107 {
109 return { SEG{ { aBox.GetLeft(), aBox.GetTop() },
110 { aBox.GetRight(), aBox.GetTop() } } };
112 return { SEG{ { aBox.GetRight(), aBox.GetTop() },
113 { aBox.GetRight(), aBox.GetBottom() } } };
115 return { SEG{ { aBox.GetLeft(), aBox.GetBottom() },
116 { aBox.GetRight(), aBox.GetBottom() } } };
118 return { SEG{ { aBox.GetLeft(), aBox.GetTop() },
119 { aBox.GetLeft(), aBox.GetBottom() } } };
121 return { SEG{ { aBox.GetLeft(), aBox.GetTop() },
122 { aBox.GetRight(), aBox.GetTop() } },
123 SEG{ { aBox.GetRight(), aBox.GetTop() },
124 { aBox.GetRight(), aBox.GetBottom() } } };
126 return { SEG{ { aBox.GetLeft(), aBox.GetBottom() },
127 { aBox.GetRight(), aBox.GetBottom() } },
128 SEG{ { aBox.GetRight(), aBox.GetTop() },
129 { aBox.GetRight(), aBox.GetBottom() } } };
131 return { SEG{ { aBox.GetLeft(), aBox.GetBottom() },
132 { aBox.GetRight(), aBox.GetBottom() } },
133 SEG{ { aBox.GetLeft(), aBox.GetTop() },
134 { aBox.GetLeft(), aBox.GetBottom() } } };
136 return { SEG{ { aBox.GetLeft(), aBox.GetTop() },
137 { aBox.GetRight(), aBox.GetTop() } },
138 SEG{ { aBox.GetLeft(), aBox.GetTop() },
139 { aBox.GetLeft(), aBox.GetBottom() } } };
142 }
143 // clang-format on
144
145 wxASSERT( false );
146 return {};
147};
148
149
150std::optional<SEG> KIGEOM::ClipHalfLineToBox( const HALF_LINE& aRay, const BOX2I& aBox )
151{
152 // Do the naive implementation - if this really is done in a tight loop,
153 // the Cohen-Sutherland implementation in ClipLine could be faster, but
154 // needs to be adapted to work with half-lines.
155 const std::array<SEG, 4> boxSegs = KIGEOM::BoxToSegs( aBox );
156
157 std::optional<VECTOR2I> ptA, ptB;
158
159 for( const SEG& boxSeg : boxSegs )
160 {
161 OPT_VECTOR2I intersection = aRay.Intersect( boxSeg );
162
163 if( !intersection )
164 {
165 continue;
166 }
167
168 // Init the first point or eat it if it's the same
169 if( !ptA || *intersection == *ptA )
170 {
171 ptA = *intersection;
172 }
173 else
174 {
175 ptB = *intersection;
176 }
177 }
178
179 // If we have exactly two intersections, the ray crossed twice
180 // so take the segment between the two points
181 if( ptA && ptB )
182 {
183 return SEG( *ptA, *ptB );
184 }
185
186 // It only crosses once, so the start is in the box. Take the segment from
187 // the start point to the intersection
188 if( ptA && *ptA != aRay.GetStart() )
189 {
190 return SEG( aRay.GetStart(), *ptA );
191 }
192
193 // It didn't cross at all
194 return std::nullopt;
195}
196
197
198std::optional<SEG> KIGEOM::ClipLineToBox( const LINE& aLine, const BOX2I& aBox )
199{
200 // As above, maybe can be optimised?
201 const std::array<SEG, 4> boxSegs = KIGEOM::BoxToSegs( aBox );
202
203 std::optional<VECTOR2I> ptA, ptB;
204
205 for( const SEG& boxSeg : boxSegs )
206 {
207 OPT_VECTOR2I intersection = aLine.Intersect( boxSeg );
208
209 // Reject intersections that are not on the actual box boundary
210 if( intersection && boxSeg.Contains( *intersection ) )
211 {
212 // Init the first point or eat it if it's the same
213 if( !ptA || *intersection == *ptA )
214 {
215 ptA = *intersection;
216 }
217 else
218 {
219 ptB = *intersection;
220 }
221 }
222 }
223
224 // If we have exactly two intersections, we have a segment
225 // (zero is no intersection, and one is a just crossing a corner exactly)
226 if( ptA && ptB )
227 {
228 return SEG( *ptA, *ptB );
229 }
230
231 return std::nullopt;
232}
233
234
236{
237 switch( aDir )
238 {
239 case DIRECTION_45::NW:
240 return SHAPE_ARC{
241 aCenter,
242 aCenter + VECTOR2I( -aRadius, 0 ),
243 ANGLE_90,
244 };
245 case DIRECTION_45::NE:
246 return SHAPE_ARC{
247 aCenter,
248 aCenter + VECTOR2I( 0, -aRadius ),
249 ANGLE_90,
250 };
251 case DIRECTION_45::SW:
252 return SHAPE_ARC{
253 aCenter,
254 aCenter + VECTOR2I( 0, aRadius ),
255 ANGLE_90,
256 };
257 case DIRECTION_45::SE:
258 return SHAPE_ARC{
259 aCenter,
260 aCenter + VECTOR2I( aRadius, 0 ),
261 ANGLE_90,
262 };
263 default: wxFAIL_MSG( "Invalid direction" ); return SHAPE_ARC();
264 }
265}
266
267
268SHAPE_ARC KIGEOM::MakeArcCw180( const VECTOR2I& aCenter, int aRadius,
270{
271 switch( aDir )
272 {
273 case DIRECTION_45::N:
274 return SHAPE_ARC{
275 aCenter,
276 aCenter + VECTOR2I( -aRadius, 0 ),
277 ANGLE_180,
278 };
279 case DIRECTION_45::E:
280 return SHAPE_ARC{
281 aCenter,
282 aCenter + VECTOR2I( 0, -aRadius ),
283 ANGLE_180,
284 };
285 case DIRECTION_45::S:
286 return SHAPE_ARC{
287 aCenter,
288 aCenter + VECTOR2I( aRadius, 0 ),
289 ANGLE_180,
290 };
291 case DIRECTION_45::W:
292 return SHAPE_ARC{
293 aCenter,
294 aCenter + VECTOR2I( 0, aRadius ),
295 ANGLE_180,
296 };
297 default: wxFAIL_MSG( "Invalid direction" );
298 }
299
300 return SHAPE_ARC();
301}
302
303
305{
306 const VECTOR2I nw = aRect.GetPosition();
307 switch( aDir )
308 {
309 // clang-format off
310 case DIRECTION_45::N:
311 return nw + VECTOR2I( aRect.GetWidth() / 2, -aOutset );
312 case DIRECTION_45::E:
313 return nw + VECTOR2I( aRect.GetWidth() + aOutset, aRect.GetHeight() / 2 );
314 case DIRECTION_45::S:
315 return nw + VECTOR2I( aRect.GetWidth() / 2, aRect.GetHeight() + aOutset );
316 case DIRECTION_45::W:
317 return nw + VECTOR2I( -aOutset, aRect.GetHeight() / 2 );
318 case DIRECTION_45::NW:
319 return nw + VECTOR2I( -aOutset, -aOutset );
320 case DIRECTION_45::NE:
321 return nw + VECTOR2I( aRect.GetWidth() + aOutset, -aOutset );
322 case DIRECTION_45::SW:
323 return nw + VECTOR2I( -aOutset, aRect.GetHeight() + aOutset );
324 case DIRECTION_45::SE:
325 return nw + VECTOR2I( aRect.GetWidth() + aOutset, aRect.GetHeight() + aOutset );
326 default:
327 wxFAIL_MSG( "Invalid direction" );
328 // clang-format on
329 }
330 return VECTOR2I();
331}
332
333
334std::vector<TYPED_POINT2I> KIGEOM::GetCircleKeyPoints( const CIRCLE& aCircle, bool aIncludeCenter )
335{
336 std::vector<TYPED_POINT2I> pts;
337
338 if( aIncludeCenter )
339 {
340 pts.emplace_back( VECTOR2I{ 0, 0 }, POINT_TYPE::PT_CENTER );
341 }
342
343 pts.emplace_back( VECTOR2I{ 0, aCircle.Radius }, POINT_TYPE::PT_QUADRANT );
344 pts.emplace_back( VECTOR2I{ aCircle.Radius, 0 }, POINT_TYPE::PT_QUADRANT );
345 pts.emplace_back( VECTOR2I{ 0, -aCircle.Radius }, POINT_TYPE::PT_QUADRANT );
346 pts.emplace_back( VECTOR2I{ -aCircle.Radius, 0 }, POINT_TYPE::PT_QUADRANT );
347
348 // Shift the points to the circle center
349 for( TYPED_POINT2I& pt : pts )
350 {
351 pt.m_point += aCircle.Center;
352 }
353
354 return pts;
355}
356
357
359{
360 SHAPE_LINE_CHAIN raOutline;
361
362 const auto handleSegment = [&]( const SEG& aSeg )
363 {
364 const VECTOR2I p0( aSeg.A.x, aSeg.B.y );
365 const VECTOR2I p1( aSeg.B.x, aSeg.A.y );
366
367 raOutline.Append( aSeg.A );
368 if( !aPoly.PointInside( p0 ) )
369 raOutline.Append( p0 );
370 else
371 raOutline.Append( p1 );
372 };
373
374 for( int i = 0; i < aPoly.SegmentCount(); i++ )
375 {
376 handleSegment( aPoly.CSegment( i ) );
377 }
378
379 // Manually handle the last segment if not closed
380 if( !aPoly.IsClosed() && aPoly.PointCount() >= 2 )
381 {
382 handleSegment( SEG( aPoly.CLastPoint(), aPoly.CPoint( 0 ) ) );
383 }
384
385 raOutline.SetClosed( true );
386 raOutline.Simplify();
387
388 return raOutline;
389}
390
391
393{
394 if( aHole.PointCount() < 3 || aHole.Area() == 0 )
395 {
396 return false;
397 }
398
399 aOutline.AddHole( std::move( aHole ) );
400 return true;
401}
402
403
404std::vector<VECTOR2I> KIGEOM::MakeRegularPolygonPoints( const VECTOR2I& aCenter, size_t aN,
405 const VECTOR2I& aPt0 )
406{
407 VECTOR2D pt0FromC = aPt0 - aCenter;
408 std::vector<VECTOR2I> pts;
409
410 for( size_t i = 0; i < aN; i++ )
411 {
412 VECTOR2D pt = GetRotated( pt0FromC, ( FULL_CIRCLE / double( aN ) ) * i );
413 pts.push_back( KiROUND( pt + aCenter ) );
414 }
415
416 return pts;
417}
418
419
420std::vector<VECTOR2I> KIGEOM::MakeRegularPolygonPoints( const VECTOR2I& aCenter, size_t aN,
421 int aRadius, bool aAcrossCorners,
422 EDA_ANGLE aAngle )
423{
424 if( !aAcrossCorners )
425 {
426 // if across flats, increase the radius
427 aRadius = aRadius / ( FULL_CIRCLE / ( aN * 2 ) ).Cos();
428 }
429
430 const VECTOR2I pt0 = aCenter + GetRotated( VECTOR2I{ aRadius, 0 }, aAngle );
431 return KIGEOM::MakeRegularPolygonPoints( aCenter, aN, pt0 );
432}
433
434
435std::vector<SEG> KIGEOM::MakeCrossSegments( const VECTOR2I& aCenter, const VECTOR2I& aSize,
436 EDA_ANGLE aAngle )
437{
438 std::vector<SEG> segs;
439
440 VECTOR2I pt0 = aCenter - GetRotated( VECTOR2I{ aSize.x / 2, 0 }, aAngle );
441 segs.emplace_back( pt0, aCenter - ( pt0 - aCenter ) );
442
443 pt0 = aCenter - GetRotated( VECTOR2I{ 0, aSize.y / 2 }, aAngle );
444 segs.emplace_back( pt0, aCenter - ( pt0 - aCenter ) );
445
446 return segs;
447}
BOX2< VECTOR2I > BOX2I
Definition box2.h:918
constexpr BOX2I KiROUND(const BOX2D &aBoxD)
Definition box2.h:986
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 coord_type GetBottom() const
Definition box2.h:218
Represent basic circle geometry with utility geometry functions.
Definition circle.h:33
VECTOR2I Center
Public to make access simpler.
Definition circle.h:150
int Radius
Public to make access simpler.
Definition circle.h:149
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:53
const VECTOR2I & GetStart() const
Get the start point of the ray.
Definition half_line.h:53
Definition line.h:32
OPT_VECTOR2I Intersect(const SEG &aOther) const
Definition line.cpp:22
Definition seg.h:38
VECTOR2I A
Definition seg.h:45
VECTOR2I B
Definition seg.h:46
SEG Reversed() const
Returns the center point of the line.
Definition seg.h:369
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.
bool PointInside(const VECTOR2I &aPt, int aAccuracy=0, bool aUseBBoxCache=false) const override
Check if point aP lies inside a closed shape.
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:181
const VECTOR2I & GetPosition() const
Definition shape_rect.h:165
int GetHeight() const
Definition shape_rect.h:189
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:42
@ PT_QUADRANT
The point is on a quadrant of a circle (N, E, S, W points).
Definition point_types.h:54
std::optional< VECTOR2I > OPT_VECTOR2I
Definition seg.h:35
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:73
VECTOR2< int32_t > VECTOR2I
Definition vector2d.h:683
VECTOR2< double > VECTOR2D
Definition vector2d.h:682
constexpr int LexicographicalCompare(const VECTOR2< T > &aA, const VECTOR2< T > &aB)
Definition vector2d.h:632