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