KiCad PCB EDA Suite
Loading...
Searching...
No Matches
test_arc_construction.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 modify it
7 * under the terms of the GNU General Public License as published by the
8 * Free Software Foundation, either version 3 of the License, or (at your
9 * option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful, but
12 * WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * 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 <cmath>
23
25#include <math/util.h>
26
27using namespace KIGEOM;
28
29
30namespace
31{
32
33double dist( const VECTOR2D& aA, const VECTOR2D& aB )
34{
35 return ( aA - aB ).EuclideanNorm();
36}
37
38
40double sweepDegrees( const ARC_SOLUTION& aSol )
41{
42 const VECTOR2D s = VECTOR2D( aSol.start ) - aSol.center;
43 const VECTOR2D m = VECTOR2D( aSol.mid ) - aSol.center;
44
45 return 2.0 * std::abs( std::atan2( s.Cross( m ), s.Dot( m ) ) ) * 180.0 / M_PI;
46}
47
48
50int orientation( const ARC_SOLUTION& aSol )
51{
52 const VECTOR2D s = VECTOR2D( aSol.start ) - aSol.center;
53 const VECTOR2D m = VECTOR2D( aSol.mid ) - aSol.center;
54
55 return s.Cross( m ) > 0.0 ? 1 : -1;
56}
57
58
60void checkConsistent( const ARC_SOLUTION& aSol, const VECTOR2I& aStart, const VECTOR2I& aEnd, double aTol = 1.5 )
61{
62 BOOST_REQUIRE( aSol.valid );
63 BOOST_CHECK_EQUAL( aSol.start, aStart );
64 BOOST_CHECK_EQUAL( aSol.end, aEnd );
65 BOOST_CHECK_SMALL( dist( aSol.start, aSol.center ) - aSol.radius, aTol );
66 BOOST_CHECK_SMALL( dist( aSol.end, aSol.center ) - aSol.radius, aTol );
67 BOOST_CHECK_SMALL( dist( aSol.mid, aSol.center ) - aSol.radius, aTol );
68 BOOST_CHECK_SMALL( dist( aSol.mid, aSol.start ) - dist( aSol.mid, aSol.end ), aTol );
69}
70
71} // namespace
72
73
74BOOST_AUTO_TEST_SUITE( ArcConstruction )
75
76
77BOOST_AUTO_TEST_CASE( ProjectLandsOnBisector )
78{
79 const VECTOR2I start( 0, 0 );
80 const VECTOR2I end( 1000000, 0 );
81
82 BOOST_CHECK_EQUAL( ProjectToChordBisector( start, end, { 700000, 300000 } ), VECTOR2I( 500000, 300000 ) );
83 BOOST_CHECK_EQUAL( ProjectToChordBisector( start, end, { -40, -900000 } ), VECTOR2I( 500000, -900000 ) );
84
85 // A diagonal chord, so the projection has to rotate
86 const VECTOR2I p = ProjectToChordBisector( { 0, 0 }, { 1000000, 1000000 }, { 900000, 100000 } );
87 BOOST_CHECK_SMALL( dist( p, VECTOR2D( 0, 0 ) ) - dist( p, VECTOR2D( 1000000, 1000000 ) ), 1.0 );
88 BOOST_CHECK_EQUAL( ProjectToChordBisector( start, start, { 12, 34 } ), VECTOR2I( 12, 34 ) );
89}
90
91
92BOOST_AUTO_TEST_CASE( ThroughPointsQuarterCircle )
93{
94 const VECTOR2I start( 1000000, 0 );
95 const VECTOR2I end( 0, 1000000 );
96
97 ARC_SOLUTION sol = ArcThroughPoints( start, { 707107, 707107 }, end );
98
99 checkConsistent( sol, start, end );
100 BOOST_CHECK_SMALL( sol.center.x, 1.5 );
101 BOOST_CHECK_SMALL( sol.center.y, 1.5 );
102 BOOST_CHECK_SMALL( sol.radius - 1000000.0, 1.5 );
103 BOOST_CHECK_LE( dist( sol.mid, VECTOR2D( 707107, 707107 ) ), 1.5 );
104 BOOST_CHECK_SMALL( sweepDegrees( sol ) - 90.0, 1e-2 );
105}
106
107
108BOOST_AUTO_TEST_CASE( ThroughPointsMajorArcMidIsHalfway )
109{
110 const VECTOR2I start( 1000000, 0 );
111 const VECTOR2I end( 0, 1000000 );
112
113 // The on-arc point on the far side of the chord selects the 270 degree arc
114 ARC_SOLUTION sol = ArcThroughPoints( start, { -707107, -707107 }, end );
115
116 checkConsistent( sol, start, end );
117 BOOST_CHECK_SMALL( sweepDegrees( sol ) - 270.0, 1e-2 );
118 BOOST_CHECK_LE( dist( sol.mid, VECTOR2D( -707107, -707107 ) ), 1.5 );
119}
120
121
122BOOST_AUTO_TEST_CASE( ThroughPointsDirectionFollowsOnArcPoint )
123{
124 const VECTOR2I a( 1000000, 0 );
125 const VECTOR2I b( 0, 1000000 );
126 const VECTOR2I on( 707107, 707107 );
127
128 ARC_SOLUTION fwd = ArcThroughPoints( a, on, b );
129 ARC_SOLUTION rev = ArcThroughPoints( b, on, a );
130
131 BOOST_REQUIRE( fwd.valid );
132 BOOST_REQUIRE( rev.valid );
133 BOOST_CHECK_EQUAL( orientation( fwd ), -orientation( rev ) );
134
135 // Same circle and same halfway point either way round
136 BOOST_CHECK_LE( dist( fwd.center, rev.center ), 1e-6 );
137 BOOST_CHECK_EQUAL( fwd.mid, rev.mid );
138 BOOST_CHECK_EQUAL( rev.start, b );
139 BOOST_CHECK_EQUAL( rev.end, a );
140}
141
142
143BOOST_AUTO_TEST_CASE( ThroughPointsMirroredOnArcPointMirrorsMid )
144{
145 const VECTOR2I start( 0, 0 );
146 const VECTOR2I end( 1000000, 0 );
147
148 ARC_SOLUTION up = ArcThroughPoints( start, { 300000, 200000 }, end );
149 ARC_SOLUTION down = ArcThroughPoints( start, { 300000, -200000 }, end );
150
151 checkConsistent( up, start, end );
152 checkConsistent( down, start, end );
153 BOOST_CHECK_GT( up.mid.y, 0 );
154 BOOST_CHECK_LT( down.mid.y, 0 );
155 BOOST_CHECK_EQUAL( up.mid.x, down.mid.x );
156 BOOST_CHECK_EQUAL( up.mid.y, -down.mid.y );
157}
158
159
160BOOST_AUTO_TEST_CASE( ThroughPointsDegenerateIsInvalid )
161{
162 BOOST_CHECK( !ArcThroughPoints( { 0, 0 }, { 500, 0 }, { 1000, 0 } ).valid );
163 BOOST_CHECK( !ArcThroughPoints( { 0, 0 }, { 500, 500 }, { 1000, 1000 } ).valid );
164 BOOST_CHECK( !ArcThroughPoints( { 0, 0 }, { 0, 0 }, { 1000, 1000 } ).valid );
165 BOOST_CHECK( !ArcThroughPoints( { 5, 5 }, { 700, 900 }, { 5, 5 } ).valid );
166 BOOST_CHECK( !ArcThroughPoints( { 5, 5 }, { 5, 5 }, { 5, 5 } ).valid );
167}
168
169
170BOOST_AUTO_TEST_CASE( ThroughPointsVeryShallowArc )
171{
172 // A 2 m chord with a 1 um sagitta, so the center is about 5e14 nm away
173 const VECTOR2I start( 0, 0 );
174 const VECTOR2I end( 2000000000, 0 );
175
176 ARC_SOLUTION sol = ArcThroughPoints( start, { 1000000000, 1000 }, end );
177
178 checkConsistent( sol, start, end );
179 BOOST_CHECK_EQUAL( sol.mid, VECTOR2I( 1000000000, 1000 ) );
180 BOOST_CHECK_CLOSE( sol.radius, 5.0e14, 1e-3 );
181}
182
183
184BOOST_AUTO_TEST_CASE( ThroughPointsOneNanometerSagitta )
185{
186 // The center is 2e18 nm away, beyond what a double resolves to the nanometer
187 const VECTOR2I start( -2000000000, 0 );
188 const VECTOR2I end( 2000000000, 0 );
189
190 ARC_SOLUTION sol = ArcThroughPoints( start, { 0, 1 }, end );
191
192 BOOST_REQUIRE( sol.valid );
193 BOOST_CHECK_EQUAL( sol.mid, VECTOR2I( 0, 1 ) );
194 BOOST_CHECK_CLOSE( sol.radius, 2.0e18, 1e-6 );
195}
196
197
198BOOST_AUTO_TEST_CASE( ThroughPointsNearIntegerLimits )
199{
200 const VECTOR2I start( -2000000000, 0 );
201 const VECTOR2I end( 2000000000, 0 );
202
203 ARC_SOLUTION sol = ArcThroughPoints( start, { 0, 1500000000 }, end );
204
205 checkConsistent( sol, start, end, 4.0 );
206 BOOST_CHECK_EQUAL( sol.mid, VECTOR2I( 0, 1500000000 ) );
207 BOOST_CHECK_CLOSE( sol.radius, ( 4.0e18 + 2.25e18 ) / 3.0e9, 1e-6 );
208}
209
210
211BOOST_AUTO_TEST_CASE( OppositeCornersNearBillionUnits )
212{
213 // Squared lengths here exceed the range of 32 and 64 bit integers alike when computed carelessly
214 const VECTOR2I a( -1000000000, -1000000000 );
215 const VECTOR2I b( 1000000000, 1000000000 );
216
217 ARC_SOLUTION through = ArcThroughPoints( a, { 1000000000, -1000000000 }, b );
218 checkConsistent( through, a, b, 4.0 );
219 BOOST_CHECK_SMALL( sweepDegrees( through ) - 180.0, 1e-2 );
220
221 ARC_SOLUTION mid = ArcFromStartEndMidDrag( a, b, { 300000000, -700000000 } );
222 checkConsistent( mid, a, b, 4.0 );
223
224 ARC_SOLUTION center = ArcFromStartEndCenterDrag( a, b, { 0, 0 }, false );
225 checkConsistent( center, a, b, 4.0 );
226 BOOST_CHECK_SMALL( sweepDegrees( center ) - 180.0, 1e-2 );
227
228 ARC_SOLUTION tangent = ArcFromStartTangentEnd( a, { 1, 0 }, b );
229 checkConsistent( tangent, a, b, 4.0 );
230 BOOST_CHECK( std::isfinite( tangent.center.x ) && std::isfinite( tangent.center.y ) );
231
232 const VECTOR2I p = ProjectToChordBisector( a, b, { 1000000000, -1000000000 } );
233 BOOST_CHECK_LE( std::abs( dist( p, VECTOR2D( a ) ) - dist( p, VECTOR2D( b ) ) ), 1.0 );
234}
235
236
237BOOST_AUTO_TEST_CASE( MidDragProjectsOntoBisector )
238{
239 const VECTOR2I start( 0, 0 );
240 const VECTOR2I end( 1000000, 0 );
241
242 ARC_SOLUTION sol = ArcFromStartEndMidDrag( start, end, { 700000, 300000 } );
243
244 checkConsistent( sol, start, end );
245 BOOST_CHECK_EQUAL( sol.mid, VECTOR2I( 500000, 300000 ) );
246 BOOST_CHECK_CLOSE( sol.radius, ( 500000.0 * 500000.0 + 300000.0 * 300000.0 ) / 600000.0, 1e-6 );
247
248 // Off the bisector on the other side of the chord
249 sol = ArcFromStartEndMidDrag( start, end, { 100, -250000 } );
250
251 checkConsistent( sol, start, end );
252 BOOST_CHECK_EQUAL( sol.mid, VECTOR2I( 500000, -250000 ) );
253}
254
255
256BOOST_AUTO_TEST_CASE( MidDragDiagonalChordIsEquidistant )
257{
258 const VECTOR2I start( 123457, -98765 );
259 const VECTOR2I end( 1000003, 777777 );
260
261 ARC_SOLUTION sol = ArcFromStartEndMidDrag( start, end, { 100000, 900000 } );
262
263 checkConsistent( sol, start, end );
264}
265
266
267BOOST_AUTO_TEST_CASE( MidDragOnChordIsInvalid )
268{
269 BOOST_CHECK( !ArcFromStartEndMidDrag( { 0, 0 }, { 1000000, 0 }, { 300000, 0 } ).valid );
270 BOOST_CHECK( !ArcFromStartEndMidDrag( { 0, 0 }, { 1000000, 1000000 }, { 900000, 900000 } ).valid );
271 BOOST_CHECK( !ArcFromStartEndMidDrag( { 7, 7 }, { 7, 7 }, { 900000, 900000 } ).valid );
272}
273
274
275BOOST_AUTO_TEST_CASE( CenterDragMinorIsDefault )
276{
277 const VECTOR2I start( 0, 0 );
278 const VECTOR2I end( 1000000, 0 );
279
280 ARC_SOLUTION sol = ArcFromStartEndCenterDrag( start, end, { 500000, 500000 }, false );
281
282 checkConsistent( sol, start, end );
283 BOOST_CHECK_LE( dist( sol.center, VECTOR2D( 500000, 500000 ) ), 1e-6 );
284 BOOST_CHECK_SMALL( sweepDegrees( sol ) - 90.0, 1e-2 );
285 BOOST_CHECK_LE( dist( sol.mid, VECTOR2D( 500000, 500000 - 707107 ) ), 1.5 );
286}
287
288
289BOOST_AUTO_TEST_CASE( CenterDragMajorSweepsPastHalfTurn )
290{
291 const VECTOR2I start( 0, 0 );
292 const VECTOR2I end( 1000000, 0 );
293
294 ARC_SOLUTION sol = ArcFromStartEndCenterDrag( start, end, { 500000, 500000 }, true );
295
296 checkConsistent( sol, start, end );
297 BOOST_CHECK_SMALL( sweepDegrees( sol ) - 270.0, 1e-2 );
298 BOOST_CHECK_LE( dist( sol.mid, VECTOR2D( 500000, 500000 + 707107 ) ), 1.5 );
299
300 // A center behind the chord swaps which side each posture bulges toward
301 ARC_SOLUTION below = ArcFromStartEndCenterDrag( start, end, { 500000, -500000 }, false );
302
303 checkConsistent( below, start, end );
304 BOOST_CHECK_SMALL( sweepDegrees( below ) - 90.0, 1e-2 );
305 BOOST_CHECK_GT( below.mid.y, 0 );
306}
307
308
309BOOST_AUTO_TEST_CASE( CenterDragOnChordIsSemicircle )
310{
311 const VECTOR2I start( 0, 0 );
312 const VECTOR2I end( 1000000, 0 );
313
314 ARC_SOLUTION minor = ArcFromStartEndCenterDrag( start, end, { 500000, 0 }, false );
315 ARC_SOLUTION major = ArcFromStartEndCenterDrag( start, end, { 500000, 0 }, true );
316
317 checkConsistent( minor, start, end );
318 checkConsistent( major, start, end );
319 BOOST_CHECK_SMALL( sweepDegrees( minor ) - 180.0, 1e-2 );
320 BOOST_CHECK_SMALL( sweepDegrees( major ) - 180.0, 1e-2 );
321 BOOST_CHECK_EQUAL( minor.mid.x, 500000 );
322 BOOST_CHECK_EQUAL( major.mid.x, 500000 );
323 BOOST_CHECK_EQUAL( minor.mid.y, -major.mid.y );
324 BOOST_CHECK_EQUAL( std::abs( minor.mid.y ), 500000 );
325}
326
327
328BOOST_AUTO_TEST_CASE( CenterDragDegenerateAndOverflowAreInvalid )
329{
330 BOOST_CHECK( !ArcFromStartEndCenterDrag( { 3, 3 }, { 3, 3 }, { 500, 500 }, false ).valid );
331
332 // The major arc apex lands outside the coordinate range, the minor arc still fits
333 BOOST_CHECK( !ArcFromStartEndCenterDrag( { -2000000000, 0 }, { 2000000000, 0 }, { 0, 2000000000 }, true ).valid );
334 BOOST_CHECK( ArcFromStartEndCenterDrag( { -2000000000, 0 }, { 2000000000, 0 }, { 0, 2000000000 }, false ).valid );
335}
336
337
338BOOST_AUTO_TEST_CASE( TangentStartDirectionIsParallelOverEndSweep )
339{
340 const VECTOR2I start( 250000, -130000 );
341
342 for( const VECTOR2D& tangent : { VECTOR2D( 1, 0 ), VECTOR2D( 0, -1 ), VECTOR2D( 3, 4 ), VECTOR2D( -5, 2 ) } )
343 {
344 const VECTOR2D unit = tangent / tangent.EuclideanNorm();
345
346 // Ends ahead of, beside and behind the start, at a spread of distances
347 for( int degrees = 5; degrees < 360; degrees += 10 )
348 {
349 for( double radius : { 3000.0, 400000.0, 90000000.0 } )
350 {
351 const double a = degrees * M_PI / 180.0;
352 const VECTOR2I end( start.x + KiROUND( radius * ( unit.x * std::cos( a ) - unit.y * std::sin( a ) ) ),
353 start.y + KiROUND( radius * ( unit.x * std::sin( a ) + unit.y * std::cos( a ) ) ) );
354
355 ARC_SOLUTION sol = ArcFromStartTangentEnd( start, tangent, end );
356
357 BOOST_TEST_CONTEXT( "tangent " << tangent << " degrees " << degrees << " radius " << radius )
358 {
359 checkConsistent( sol, start, end, 2.0 );
360
361 const VECTOR2D radial = ( VECTOR2D( start ) - sol.center ) / sol.radius;
362 const VECTOR2D travel = static_cast<double>( orientation( sol ) ) * VECTOR2D( -radial.y, radial.x );
363
364 BOOST_CHECK_SMALL( radial.Dot( unit ) * sol.radius, 1.0 );
365 BOOST_CHECK_GT( travel.Dot( unit ), 0.999999 );
366 }
367 }
368 }
369 }
370}
371
372
373BOOST_AUTO_TEST_CASE( TangentSweepIsTwiceTheChordAngle )
374{
375 const VECTOR2I start( 0, 0 );
376
377 // Ahead and to the side gives a quarter turn's chord at 45 degrees, so 90 degrees of sweep
378 BOOST_CHECK_SMALL( sweepDegrees( ArcFromStartTangentEnd( start, { 1, 0 }, { 100000, 100000 } ) ) - 90.0, 1e-2 );
379
380 // Behind the start the arc has to swing around the long way
381 BOOST_CHECK_SMALL( sweepDegrees( ArcFromStartTangentEnd( start, { 1, 0 }, { -100000, 100000 } ) ) - 270.0, 1e-2 );
382}
383
384
385BOOST_AUTO_TEST_CASE( TangentSideFollowsEndSide )
386{
387 ARC_SOLUTION left = ArcFromStartTangentEnd( { 0, 0 }, { 1, 0 }, { 100000, 100000 } );
388 ARC_SOLUTION right = ArcFromStartTangentEnd( { 0, 0 }, { 1, 0 }, { 100000, -100000 } );
389
390 BOOST_REQUIRE( left.valid );
391 BOOST_REQUIRE( right.valid );
392 BOOST_CHECK_GT( left.center.y, 0.0 );
393 BOOST_CHECK_LT( right.center.y, 0.0 );
394 BOOST_CHECK_EQUAL( left.mid.x, right.mid.x );
395 BOOST_CHECK_EQUAL( left.mid.y, -right.mid.y );
396}
397
398
399BOOST_AUTO_TEST_CASE( TangentDegenerateIsInvalid )
400{
401 // End on the tangent line, ahead of and behind the start
402 BOOST_CHECK( !ArcFromStartTangentEnd( { 0, 0 }, { 1, 0 }, { 500000, 0 } ).valid );
403 BOOST_CHECK( !ArcFromStartTangentEnd( { 0, 0 }, { 1, 0 }, { -500000, 0 } ).valid );
404 BOOST_CHECK( !ArcFromStartTangentEnd( { 0, 0 }, { 0, 0 }, { 500000, 500000 } ).valid );
405 BOOST_CHECK( !ArcFromStartTangentEnd( { 10, 10 }, { 1, 1 }, { 10, 10 } ).valid );
406 BOOST_CHECK( !ArcFromStartTangentEnd( { 0, 0 }, { 1, 1 }, { 500000, 500000 } ).valid );
407}
408
409
constexpr BOX2I KiROUND(const BOX2D &aBoxD)
Definition box2.h:982
constexpr extended_type Cross(const VECTOR2< T > &aVector) const
Compute cross product of self with aVector.
Definition vector2d.h:559
constexpr extended_type Dot(const VECTOR2< T > &aVector) const
Compute dot product of self with aVector.
Definition vector2d.h:567
Construction helpers for the interactive arc drawing modes.
ARC_SOLUTION ArcFromStartEndCenterDrag(const VECTOR2I &aStart, const VECTOR2I &aEnd, const VECTOR2I &aCursor, bool aMajor)
Arc from aStart to aEnd centered on aCursor projected onto the chord bisector.
VECTOR2I ProjectToChordBisector(const VECTOR2I &aStart, const VECTOR2I &aEnd, const VECTOR2I &aCursor)
Project aCursor onto the perpendicular bisector of the chord aStart - aEnd.
ARC_SOLUTION ArcFromStartTangentEnd(const VECTOR2I &aStart, const VECTOR2D &aTangent, const VECTOR2I &aEnd)
Arc leaving aStart along aTangent and ending at aEnd.
ARC_SOLUTION ArcFromStartEndMidDrag(const VECTOR2I &aStart, const VECTOR2I &aEnd, const VECTOR2I &aCursor)
Arc from aStart to aEnd whose midpoint is aCursor projected onto the chord bisector.
ARC_SOLUTION ArcThroughPoints(const VECTOR2I &aStart, const VECTOR2I &aOnArc, const VECTOR2I &aEnd)
Arc from aStart to aEnd that passes through aOnArc.
EDA_ANGLE abs(const EDA_ANGLE &aAngle)
Definition eda_angle.h:437
bool valid
False for degenerate input (collinear points, zero chord, etc)
VECTOR2I mid
The point on the arc halfway along the sweep.
BOOST_AUTO_TEST_CASE(HorizontalAlignment)
BOOST_AUTO_TEST_CASE(ProjectLandsOnBisector)
BOOST_AUTO_TEST_SUITE(CadstarPartParser)
BOOST_REQUIRE(intersection.has_value()==c.ExpectedIntersection.has_value())
BOOST_AUTO_TEST_SUITE_END()
VECTOR2I center
int radius
VECTOR2I end
BOOST_TEST_CONTEXT("Test Clearance")
BOOST_CHECK_EQUAL(result, "25.4")
#define M_PI
VECTOR2< int32_t > VECTOR2I
Definition vector2d.h:708
VECTOR2< double > VECTOR2D
Definition vector2d.h:707