KiCad PCB EDA Suite
Loading...
Searching...
No Matches
geometry_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 (C) 2018 Jean-Pierre Charras, jp.charras at wanadoo.fr
5 * Copyright The KiCad Developers, see AUTHORS.txt for contributors.
6 *
7 * This program is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License
9 * as published by the Free Software Foundation; either version 2
10 * of the License, or (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program. If not, see <https://www.gnu.org/licenses/>.
19 */
20
25
26#include <cstdint>
27#include <algorithm> // for max, min
28
30#include <math/util.h> // for KiROUND
31
32// To approximate a circle by segments, a minimal seg count is mandatory.
33// Note that this is rarely used as the maxError constraint will yield a higher
34// segment count on everything but very small circles. (Even a 0.125mm track
35// with a 0.01mm maximum deviation yields 11 segments.)
36#define MIN_SEGCOUNT_FOR_CIRCLE 8
37
38int GetArcToSegmentCount( int aRadius, int aErrorMax, const EDA_ANGLE& aArcAngle )
39{
40 // calculate the number of segments to approximate a circle by segments
41 // given the max distance between the middle of a segment and the circle
42
43 // avoid divide-by-zero
44 aRadius = std::max( 1, aRadius );
45 aErrorMax = std::max( 1, aErrorMax );
46
47 // error relative to the radius value:
48 double rel_error = (double)aErrorMax / aRadius;
49 // minimal arc increment in degrees:
50 double arc_increment = 180 / M_PI * acos( 1.0 - rel_error ) * 2;
51
52 // Ensure a minimal arc increment reasonable value for a circle
53 // (360.0 degrees). For very small radius values, this is mandatory.
54 arc_increment = std::min( 360.0/MIN_SEGCOUNT_FOR_CIRCLE, arc_increment );
55
56 int segCount = KiROUND( fabs( aArcAngle.AsDegrees() ) / arc_increment );
57
58 // Ensure at least two segments are used for algorithmic safety
59 return std::max( segCount, 2 );
60}
61
62
63int CircleToEndSegmentDeltaRadius( int aRadius, int aSegCount )
64{
65 // The minimal seg count is 3, otherwise we cannot calculate the result
66 // in practice, the min count is clamped to 8 in kicad
67 if( aSegCount <= 2 )
68 aSegCount = 3;
69
70 // The angle between the center of the segment and one end of the segment
71 // when the circle is approximated by aSegCount segments
72 double alpha = M_PI / aSegCount;
73
74 // aRadius is the radius of the circle tangent to the middle of each segment
75 // and aRadius/cos(aplha) is the radius of the circle defined by seg ends
76 int delta = KiROUND( std::abs( aRadius * ( 1 - 1/cos( alpha ) ) ) );
77
78 return delta;
79}
80
81// When creating polygons to create a clearance polygonal area, the polygon must
82// be same or bigger than the original shape.
83// Polygons are bigger if the original shape has arcs (round rectangles, ovals,
84// circles...). However, when building the solder mask layer modifying the shapes
85// when converting them to polygons is not acceptable (the modification can break
86// calculations).
87// So one can disable the shape expansion within a particular scope by allocating
88// a DISABLE_ARC_CORRECTION.
89
90static bool s_disable_arc_correction = false;
91
96
101
102int GetCircleToPolyCorrection( int aMaxError )
103{
104 // Push all the error to the outside by increasing the radius
105 return s_disable_arc_correction ? 0 : aMaxError;
106}
107
108
109/***
110 * Utility for the line clipping code, returns the boundary code of
111 * a point. Bit allocation is arbitrary
112 */
113inline int clipOutCode( const BOX2I *aClipBox, int x, int y )
114{
115 int code;
116
117 if( y < aClipBox->GetY() )
118 code = 2;
119 else if( y > aClipBox->GetBottom() )
120 code = 1;
121 else
122 code = 0;
123
124 if( x < aClipBox->GetX() )
125 code |= 4;
126 else if( x > aClipBox->GetRight() )
127 code |= 8;
128
129 return code;
130}
131
132
133bool ClipLine( const BOX2I *aClipBox, int &x1, int &y1, int &x2, int &y2 )
134{
135 // Stock Cohen-Sutherland algorithm; check *any* CG book for details
136 int outcode1 = clipOutCode( aClipBox, x1, y1 );
137 int outcode2 = clipOutCode( aClipBox, x2, y2 );
138
139 while( outcode1 || outcode2 )
140 {
141 // Fast reject
142 if( outcode1 & outcode2 )
143 return true;
144
145 // Choose a side to clip
146 int thisoutcode, x, y;
147
148 if( outcode1 )
149 thisoutcode = outcode1;
150 else
151 thisoutcode = outcode2;
152
153 /* One clip round
154 * Since we use the full range of 32 bit ints, the proportion
155 * computation has to be done in 64 bits to avoid horrible
156 * results */
157 if( thisoutcode & 1 ) // Clip the bottom
158 {
159 y = aClipBox->GetBottom();
160 x = x1 + (x2 - x1) * std::int64_t(y - y1) / (y2 - y1);
161 }
162 else if( thisoutcode & 2 ) // Clip the top
163 {
164 y = aClipBox->GetY();
165 x = x1 + ( x2 - x1 ) * std::int64_t( y - y1 ) / ( y2 - y1 );
166 }
167 else if( thisoutcode & 8 ) // Clip the right
168 {
169 x = aClipBox->GetRight();
170 y = y1 + ( y2 - y1 ) * std::int64_t( x - x1 ) / ( x2 - x1 );
171 }
172 else // if( thisoutcode & 4), obviously, clip the left
173 {
174 x = aClipBox->GetX();
175 y = y1 + ( y2 - y1 ) * std::int64_t( x - x1 ) / ( x2 - x1 );
176 }
177
178 // Put the result back and update the boundary code
179 // No ambiguity, otherwise it would have been a fast reject
180 if( thisoutcode == outcode1 )
181 {
182 x1 = x;
183 y1 = y;
184 outcode1 = clipOutCode( aClipBox, x1, y1 );
185 }
186 else
187 {
188 x2 = x;
189 y2 = y;
190 outcode2 = clipOutCode( aClipBox, x2, y2 );
191 }
192 }
193
194 return false;
195}
196
197
198bool KIGEOM::BoxHitTest( const VECTOR2I& aHitter, const BOX2I& aHittee, int aAccuracy )
199{
200 const BOX2I hittee = aHittee.GetInflated( aAccuracy );
201 return hittee.Contains( aHitter );
202}
203
204
205bool KIGEOM::BoxHitTest( const BOX2I& aHitter, const BOX2I& aHittee, bool aHitteeContained,
206 int aAccuracy )
207{
208 const BOX2I hitter = aHitter.GetInflated( aAccuracy );
209
210 if( aHitteeContained )
211 return hitter.Contains( aHittee );
212
213 return hitter.Intersects( aHittee );
214}
215
216
217bool KIGEOM::BoxHitTest( const SHAPE_LINE_CHAIN& aHitter, const BOX2I& aHittee, bool aHitteeContained )
218{
219 SHAPE_RECT bbox( aHittee );
220
221 return KIGEOM::ShapeHitTest( aHitter, bbox, aHitteeContained );
222}
223
224
225bool KIGEOM::BoxHitTest( const SHAPE_LINE_CHAIN& aHitter, const BOX2I& aHittee, const EDA_ANGLE& aHitteeRotation,
226 const VECTOR2I& aHitteeRotationCenter, bool aHitteeContained )
227{
228 // Optimization: use SHAPE_RECT collision test if possible
229 if( aHitteeRotation.IsZero() )
230 {
231 return KIGEOM::BoxHitTest( aHitter, aHittee, aHitteeContained );
232 }
233 else if( aHitteeRotation.IsCardinal() )
234 {
235 BOX2I box = aHittee.GetBoundingBoxRotated( aHitteeRotationCenter, aHitteeRotation );
236 return KIGEOM::BoxHitTest( aHitter, box, aHitteeContained );
237 }
238
239 // Non-cardinal angle: convert to simple polygon and rotate
240 const std::vector<VECTOR2I> corners =
241 {
242 aHittee.GetOrigin(),
243 VECTOR2I( aHittee.GetRight(), aHittee.GetTop() ),
244 aHittee.GetEnd(),
245 VECTOR2I( aHittee.GetLeft(), aHittee.GetBottom() )
246 };
247
248 SHAPE_SIMPLE shape( corners );
249 shape.Rotate( aHitteeRotation, aHitteeRotationCenter );
250
251 return KIGEOM::ShapeHitTest( aHitter, shape, aHitteeContained );
252}
253
254
255bool KIGEOM::ShapeHitTest( const SHAPE_LINE_CHAIN& aHitter, const SHAPE& aHittee, bool aHitteeContained )
256{
257 // Check if the selection polygon collides with any of the hittee's subshapes.
258 auto collidesAny =
259 [&]()
260 {
261 return aHittee.Collide( &aHitter );
262 };
263
264 // Check if the selection polygon collides with all of the hittee's subshapes.
265 auto collidesAll =
266 [&]()
267 {
268 if( const auto compoundHittee = dynamic_cast<const SHAPE_COMPOUND*>( &aHittee ) )
269 {
270 // If the hittee is a compound shape, all subshapes must collide.
271 return std::ranges::all_of(
272 compoundHittee->Shapes(),
273 [&]( const SHAPE* subshape )
274 {
275 return subshape && subshape->Collide( &aHitter );
276 } );
277 }
278 else
279 {
280 // If the hittee is a simple shape, we can check it directly.
281 return aHittee.Collide( &aHitter );
282 }
283 };
284
285 // Check if the selection polygon outline collides with the hittee's shape.
286 auto intersectsAny =
287 [&]()
288 {
289 const int count = aHitter.SegmentCount();
290
291 for( int i = 0; i < count; ++i )
292 {
293 if( aHittee.Collide( aHitter.CSegment( i ) ) )
294 return true;
295 }
296
297 return false;
298 };
299
300 if( aHitter.IsClosed() )
301 {
302 if( aHitteeContained )
303 // Containing polygon - all of the subshapes must collide with the selection polygon,
304 // but none of them can intersect its outline.
305 return collidesAll() && !intersectsAny();
306 else
307 // Touching polygon - any of the subshapes should collide with the selection polygon.
308 return collidesAny();
309 }
310 else
311 {
312 // Touching (poly)line - any of the subshapes should intersect the selection polyline.
313 return intersectsAny();
314 }
315}
BOX2< VECTOR2I > BOX2I
Definition box2.h:918
constexpr BOX2I KiROUND(const BOX2D &aBoxD)
Definition box2.h:986
constexpr const Vec GetEnd() const
Definition box2.h:208
constexpr coord_type GetY() const
Definition box2.h:204
constexpr coord_type GetX() const
Definition box2.h:203
constexpr coord_type GetLeft() const
Definition box2.h:224
constexpr bool Contains(const Vec &aPoint) const
Definition box2.h:164
constexpr BOX2< Vec > GetInflated(coord_type aDx, coord_type aDy) const
Get a new rectangle that is this one, inflated by aDx and aDy.
Definition box2.h:634
constexpr const Vec & GetOrigin() const
Definition box2.h:206
const BOX2< Vec > GetBoundingBoxRotated(const VECTOR2I &aRotCenter, const EDA_ANGLE &aAngle) const
Useful to calculate bounding box of rotated items, when rotation is not cardinal.
Definition box2.h:716
constexpr coord_type GetRight() const
Definition box2.h:213
constexpr coord_type GetTop() const
Definition box2.h:225
constexpr bool Intersects(const BOX2< Vec > &aRect) const
Definition box2.h:307
constexpr coord_type GetBottom() const
Definition box2.h:218
double AsDegrees() const
Definition eda_angle.h:116
bool IsZero() const
Definition eda_angle.h:136
bool IsCardinal() const
Definition eda_angle.cpp:40
Represent a polyline containing arcs as well as line segments: A chain of connected line and/or arc s...
bool IsClosed() const override
int SegmentCount() const
Return the number of segments in this line chain.
const SEG CSegment(int aIndex) const
Return a constant copy of the aIndex segment in the line chain.
Represent a simple polygon consisting of a zero-thickness closed chain of connected line segments.
void Rotate(const EDA_ANGLE &aAngle, const VECTOR2I &aCenter={ 0, 0 }) override
An abstract shape on 2D plane.
Definition shape.h:124
virtual bool Collide(const VECTOR2I &aP, int aClearance=0, int *aActual=nullptr, VECTOR2I *aLocation=nullptr) const
Check if the boundary of shape (this) lies closer to the point aP than aClearance,...
Definition shape.h:179
int GetCircleToPolyCorrection(int aMaxError)
static bool s_disable_arc_correction
int CircleToEndSegmentDeltaRadius(int aRadius, int aSegCount)
int clipOutCode(const BOX2I *aClipBox, int x, int y)
bool ClipLine(const BOX2I *aClipBox, int &x1, int &y1, int &x2, int &y2)
Test if any part of a line falls within the bounds of a rectangle.
int GetArcToSegmentCount(int aRadius, int aErrorMax, const EDA_ANGLE &aArcAngle)
#define MIN_SEGCOUNT_FOR_CIRCLE
a few functions useful in geometry calculations.
bool ShapeHitTest(const SHAPE_LINE_CHAIN &aHitter, const SHAPE &aHittee, bool aHitteeContained)
Perform a shape-to-shape hit test.
bool BoxHitTest(const VECTOR2I &aHitPoint, const BOX2I &aHittee, int aAccuracy)
Perform a point-to-box hit test.
EDA_ANGLE abs(const EDA_ANGLE &aAngle)
Definition eda_angle.h:400
int delta
#define M_PI
VECTOR2< int32_t > VECTOR2I
Definition vector2d.h:683