KiCad PCB EDA Suite
Loading...
Searching...
No Matches
test_kimath.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) 2020 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
29
30// Code under test
31#include <trigo.h>
32#include <geometry/shape_arc.h>
33
34
35/*
36CircleCenterFrom3Points calculate the center of a circle defined by 3 points
37It is similar to CalcArcCenter( const VECTOR2D& aStart, const VECTOR2D& aMid, const VECTOR2D& aEnd )
38but it was needed to debug CalcArcCenter, so I keep it available for other issues in CalcArcCenter
39
40The perpendicular bisector of the segment between two points is the
41set of all points equidistant from both. So if you take the
42perpendicular bisector of (x1,y1) and (x2,y2) and the perpendicular
43bisector of the segment from (x2,y2) to (x3,y3) and find the
44intersection of those lines, that point will be the center.
45
46To find the equation of the perpendicular bisector of (x1,y1) to (x2,y2),
47you know that it passes through the midpoint of the segment:
48((x1+x2)/2,(y1+y2)/2), and if the slope of the line
49connecting (x1,y1) to (x2,y2) is m, the slope of the perpendicular
50bisector is -1/m. Work out the equations for the two lines, find
51their intersection, and bingo! You've got the coordinates of the center.
52
53An error should occur if the three points lie on a line, and you'll
54need special code to check for the case where one of the slopes is zero.
55
56see https://web.archive.org/web/20171223103555/http://mathforum.org/library/drmath/view/54323.html
57*/
58static bool Ref0CircleCenterFrom3Points( const VECTOR2D& p1, const VECTOR2D& p2, const VECTOR2D& p3,
59 VECTOR2D* aCenter )
60{
61 // Move coordinate origin to p2, to simplify calculations
62 VECTOR2D b = p1 - p2;
63 VECTOR2D d = p3 - p2;
64 double bc = ( b.x * b.x + b.y * b.y ) / 2.0;
65 double cd = ( -d.x * d.x - d.y * d.y ) / 2.0;
66 double det = -b.x * d.y + d.x * b.y;
67
68 if( fabs( det ) < 1.0e-6 ) // arbitrary limit to avoid divide by 0
69 return false;
70
71 det = 1 / det;
72 aCenter->x = ( -bc * d.y - cd * b.y ) * det;
73 aCenter->y = ( b.x * cd + d.x * bc ) * det;
74 *aCenter += p2;
75
76 return true;
77}
78
79
80// Based on https://paulbourke.net/geometry/circlesphere/
81// Uses the Y'b equation too, depending on slope values
82static const VECTOR2D Ref1CalcArcCenter( const VECTOR2D& aStart, const VECTOR2D& aMid, const VECTOR2D& aEnd )
83{
84 VECTOR2D center;
85 double yDelta_21 = aMid.y - aStart.y;
86 double xDelta_21 = aMid.x - aStart.x;
87 double yDelta_32 = aEnd.y - aMid.y;
88 double xDelta_32 = aEnd.x - aMid.x;
89
90 // This is a special case for aMid as the half-way point when aSlope = 0 and bSlope = inf
91 // or the other way around. In that case, the center lies in a straight line between
92 // aStart and aEnd
93 if( ( ( xDelta_21 == 0.0 ) && ( yDelta_32 == 0.0 ) )
94 || ( ( yDelta_21 == 0.0 ) && ( xDelta_32 == 0.0 ) ) )
95 {
96 center.x = ( aStart.x + aEnd.x ) / 2.0;
97 center.y = ( aStart.y + aEnd.y ) / 2.0;
98 return center;
99 }
100
101 // Prevent div=0 errors
102 /*if( xDelta_21 == 0.0 )
103 xDelta_21 = std::numeric_limits<double>::epsilon();
104
105 if( xDelta_32 == 0.0 )
106 xDelta_32 = -std::numeric_limits<double>::epsilon();*/
107
108 double aSlope = yDelta_21 / xDelta_21;
109 double bSlope = yDelta_32 / xDelta_32;
110
111 if( aSlope == bSlope )
112 {
113 if( aStart == aEnd )
114 {
115 // This is a special case for a 360 degrees arc. In this case, the center is halfway between
116 // the midpoint and either newEnd point
117 center.x = ( aStart.x + aMid.x ) / 2.0;
118 center.y = ( aStart.y + aMid.y ) / 2.0;
119 return center;
120 }
121 else
122 {
123 // If the points are colinear, the center is at infinity, so offset
124 // the slope by a minimal amount
125 // Warning: This will induce a small error in the center location
126 /*aSlope += std::numeric_limits<double>::epsilon();
127 bSlope -= std::numeric_limits<double>::epsilon();*/
128 }
129 }
130
131 center.x = ( aSlope * bSlope * ( aStart.y - aEnd.y ) + bSlope * ( aStart.x + aMid.x )
132 - aSlope * ( aMid.x + aEnd.x ) )
133 / ( 2 * ( bSlope - aSlope ) );
134
135 if( std::abs( aSlope ) > std::abs( bSlope ) )
136 {
137 center.y = ( ( ( aStart.x + aMid.x ) / 2.0 - center.x ) / aSlope
138 + ( aStart.y + aMid.y ) / 2.0 );
139 }
140 else
141 {
142 center.y =
143 ( ( ( aMid.x + aEnd.x ) / 2.0 - center.x ) / bSlope + ( aMid.y + aEnd.y ) / 2.0 );
144 }
145
146 return center;
147}
148
149
151{
155};
156
157
161BOOST_AUTO_TEST_SUITE( KiMath )
162
163
164BOOST_AUTO_TEST_CASE( TestCalcArcCenter3Pts )
165{
166 // Order: start, mid, end
167 std::vector<TEST_CALC_ARC_CENTER_CASE> calc_center_cases = {
168 //{ { 183000000, 89000000 }, { 185333332, 89000000 }, { 185333333, 91333333 } } // Fails currently
169 };
170
171 const double tolerance = 1.0;
172
173 // Check random points (fails currently)
174 /*for( int i = 0; i < 100; i++ )
175 {
176 TEST_CALC_ARC_CENTER_CASE cas;
177
178 cas.istart.x = rand();
179 cas.istart.y = rand();
180
181 cas.imid.x = rand();
182 cas.imid.y = rand();
183
184 cas.iend.x = rand();
185 cas.iend.y = rand();
186
187 calc_center_cases.push_back( cas );
188 }*/
189
190 for( const TEST_CALC_ARC_CENTER_CASE& entry : calc_center_cases )
191 {
192 wxString msg;
193
194 VECTOR2D start( entry.istart );
195 VECTOR2D mid( entry.imid );
196 VECTOR2D end( entry.iend );
197
198 VECTOR2D calcCenter = CalcArcCenter( start, mid, end );
199
200 double crs = ( calcCenter - start ).EuclideanNorm();
201 double crm = ( calcCenter - mid ).EuclideanNorm();
202 double cre = ( calcCenter - end ).EuclideanNorm();
203
204 double cavg = ( crs + crm + cre ) / 3.0;
205
206 if( std::abs( crs - cavg ) > tolerance || std::abs( crm - cavg ) > tolerance
207 || std::abs( cre - cavg ) > tolerance )
208 {
209 msg << "CalcArcCenter failed.";
210
211 msg << "\nstart: " << entry.istart.Format();
212 msg << "\nmid: " << entry.imid.Format();
213 msg << "\nend: " << entry.iend.Format();
214
215 {
216 msg << "\nCalculated center: " << wxString::Format( "%.15f", calcCenter.x ) << ", "
217 << wxString::Format( "%.15f", calcCenter.y );
218
219 msg << "\n Avg radius: " << wxString::Format( "%.15f", cavg );
220 msg << "\nStart radius: " << wxString::Format( "%.15f", crs );
221 msg << "\n Mid radius: " << wxString::Format( "%.15f", crm );
222 msg << "\n End radius: " << wxString::Format( "%.15f", cre );
223 msg << "\n";
224
225 // Check mid/end points using the calculated center (like SHAPE_ARC)
226 EDA_ANGLE angStart( start - calcCenter );
227 EDA_ANGLE angMid( mid - calcCenter );
228 EDA_ANGLE angEnd( end - calcCenter );
229
230 EDA_ANGLE angCenter = angEnd - angStart;
231
232 VECTOR2D newMid = start;
233 VECTOR2D newEnd = start;
234
235 RotatePoint( newMid, calcCenter, -angCenter / 2.0 );
236 RotatePoint( newEnd, calcCenter, -angCenter );
237
238 msg << "\nNew mid: " << wxString::Format( "%.15f", newMid.x ) << ", "
239 << wxString::Format( "%.15f", newMid.y );
240
241 msg << "\nNew end: " << wxString::Format( "%.15f", newEnd.x ) << ", "
242 << wxString::Format( "%.15f", newEnd.y );
243 msg << "\n";
244
245 double endsDist = ( newEnd - end ).EuclideanNorm();
246
247 msg << "\nNew end is off by " << wxString::Format( "%.15f", endsDist );
248 msg << "\n";
249 }
250
251 {
252 VECTOR2D ref0Center;
253 Ref0CircleCenterFrom3Points( start, mid, end, &ref0Center );
254
255 double r0_rs = ( ref0Center - start ).EuclideanNorm();
256 double r0_rm = ( ref0Center - mid ).EuclideanNorm();
257 double r0_rre = ( ref0Center - end ).EuclideanNorm();
258
259 double r0_ravg = ( r0_rs + r0_rm + r0_rre ) / 3.0;
260
261 msg << "\nReference0 center: " << wxString::Format( "%.15f", ref0Center.x ) << ", "
262 << wxString::Format( "%.15f", ref0Center.y );
263
264 msg << "\nRef0 Avg radius: " << wxString::Format( "%.15f", r0_ravg );
265 msg << "\nRef0 Start radius: " << wxString::Format( "%.15f", r0_rs );
266 msg << "\nRef0 Mid radius: " << wxString::Format( "%.15f", r0_rm );
267 msg << "\nRef0 End radius: " << wxString::Format( "%.15f", r0_rre );
268 msg << "\n";
269 }
270
271 {
272 VECTOR2D ref1Center = Ref1CalcArcCenter( start, mid, end );
273
274 double r1_rs = ( ref1Center - start ).EuclideanNorm();
275 double r1_rm = ( ref1Center - mid ).EuclideanNorm();
276 double r1_rre = ( ref1Center - end ).EuclideanNorm();
277
278 double r1_ravg = ( r1_rs + r1_rm + r1_rre ) / 3.0;
279
280 msg << "\nReference1 center: " << wxString::Format( "%.15f", ref1Center.x ) << ", "
281 << wxString::Format( "%.15f", ref1Center.y );
282
283 msg << "\nRef1 Avg radius: " << wxString::Format( "%.15f", r1_ravg );
284 msg << "\nRef1 Start radius: " << wxString::Format( "%.15f", r1_rs );
285 msg << "\nRef1 Mid radius: " << wxString::Format( "%.15f", r1_rm );
286 msg << "\nRef1 End radius: " << wxString::Format( "%.15f", r1_rre );
287 msg << "\n";
288 }
289
290
291 BOOST_CHECK_MESSAGE( false, msg );
292 }
293 }
294}
295
296
297BOOST_AUTO_TEST_CASE( TestInterceptsPositiveX )
298{
299 BOOST_CHECK( !InterceptsPositiveX( 10.0, 20.0 ) );
300 BOOST_CHECK( !InterceptsPositiveX( 10.0, 120.0 ) );
301 BOOST_CHECK( !InterceptsPositiveX( 10.0, 220.0 ) );
302 BOOST_CHECK( !InterceptsPositiveX( 10.0, 320.0 ) );
303 BOOST_CHECK( InterceptsPositiveX( 20.0, 10.0 ) );
304 BOOST_CHECK( InterceptsPositiveX( 345.0, 15.0 ) );
305}
306
307
308BOOST_AUTO_TEST_CASE( TestInterceptsNegativeX )
309{
310 BOOST_CHECK( !InterceptsNegativeX( 10.0, 20.0 ) );
311 BOOST_CHECK( !InterceptsNegativeX( 10.0, 120.0 ) );
312 BOOST_CHECK( InterceptsNegativeX( 10.0, 220.0 ) );
313 BOOST_CHECK( InterceptsNegativeX( 10.0, 320.0 ) );
314 BOOST_CHECK( InterceptsNegativeX( 20.0, 10.0 ) );
315 BOOST_CHECK( !InterceptsNegativeX( 345.0, 15.0 ) );
316 BOOST_CHECK( InterceptsNegativeX( 145.0, 225.0 ) );
317}
318
319
320BOOST_AUTO_TEST_SUITE_END()
EDA_ANGLE abs(const EDA_ANGLE &aAngle)
Definition: eda_angle.h:424
BOOST_CHECK(box.ClosestPointTo(VECTOR2D(0, 0))==VECTOR2D(1, 2))
Test suite for KiCad math code.
BOOST_AUTO_TEST_SUITE(CadstarPartParser)
static const VECTOR2D Ref1CalcArcCenter(const VECTOR2D &aStart, const VECTOR2D &aMid, const VECTOR2D &aEnd)
Definition: test_kimath.cpp:82
static bool Ref0CircleCenterFrom3Points(const VECTOR2D &p1, const VECTOR2D &p2, const VECTOR2D &p3, VECTOR2D *aCenter)
Test suite for KiCad math code.
Definition: test_kimath.cpp:58
BOOST_AUTO_TEST_CASE(TestCalcArcCenter3Pts)
Declare the test suite.
void RotatePoint(int *pX, int *pY, const EDA_ANGLE &aAngle)
Calculate the new point of coord coord pX, pY, for a rotation center 0, 0.
Definition: trigo.cpp:228
bool InterceptsPositiveX(double aStartAngle, double aEndAngle)
Test if an arc from aStartAngle to aEndAngle crosses the positive X axis (0 degrees).
Definition: trigo.h:249
bool InterceptsNegativeX(double aStartAngle, double aEndAngle)
Test if an arc from aStartAngle to aEndAngle crosses the negative X axis (180 degrees).
Definition: trigo.h:267
const VECTOR2I CalcArcCenter(const VECTOR2I &aStart, const VECTOR2I &aMid, const VECTOR2I &aEnd)
Determine the center of an arc or circle given three points on its circumference.
Definition: trigo.cpp:520
double EuclideanNorm(const VECTOR2I &vector)
Definition: trigo.h:128