You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

522 lines
16 KiB

  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. /**
  24. * Test suite for KiCad math code.
  25. */
  26. #include <qa_utils/wx_utils/unit_test_utils.h>
  27. // Code under test
  28. #include <trigo.h>
  29. #include <hash_128.h>
  30. #include <geometry/shape_arc.h>
  31. /*
  32. CircleCenterFrom3Points calculate the center of a circle defined by 3 points
  33. It is similar to CalcArcCenter( const VECTOR2D& aStart, const VECTOR2D& aMid, const VECTOR2D& aEnd )
  34. but it was needed to debug CalcArcCenter, so I keep it available for other issues in CalcArcCenter
  35. The perpendicular bisector of the segment between two points is the
  36. set of all points equidistant from both. So if you take the
  37. perpendicular bisector of (x1,y1) and (x2,y2) and the perpendicular
  38. bisector of the segment from (x2,y2) to (x3,y3) and find the
  39. intersection of those lines, that point will be the center.
  40. To find the equation of the perpendicular bisector of (x1,y1) to (x2,y2),
  41. you know that it passes through the midpoint of the segment:
  42. ((x1+x2)/2,(y1+y2)/2), and if the slope of the line
  43. connecting (x1,y1) to (x2,y2) is m, the slope of the perpendicular
  44. bisector is -1/m. Work out the equations for the two lines, find
  45. their intersection, and bingo! You've got the coordinates of the center.
  46. An error should occur if the three points lie on a line, and you'll
  47. need special code to check for the case where one of the slopes is zero.
  48. see https://web.archive.org/web/20171223103555/http://mathforum.org/library/drmath/view/54323.html
  49. */
  50. static bool Ref0CircleCenterFrom3Points( const VECTOR2D& p1, const VECTOR2D& p2, const VECTOR2D& p3,
  51. VECTOR2D* aCenter )
  52. {
  53. // Move coordinate origin to p2, to simplify calculations
  54. VECTOR2D b = p1 - p2;
  55. VECTOR2D d = p3 - p2;
  56. double bc = ( b.x * b.x + b.y * b.y ) / 2.0;
  57. double cd = ( -d.x * d.x - d.y * d.y ) / 2.0;
  58. double det = -b.x * d.y + d.x * b.y;
  59. if( fabs( det ) < 1.0e-6 ) // arbitrary limit to avoid divide by 0
  60. return false;
  61. det = 1 / det;
  62. aCenter->x = ( -bc * d.y - cd * b.y ) * det;
  63. aCenter->y = ( b.x * cd + d.x * bc ) * det;
  64. *aCenter += p2;
  65. return true;
  66. }
  67. // Based on https://paulbourke.net/geometry/circlesphere/
  68. // Uses the Y'b equation too, depending on slope values
  69. static const VECTOR2D Ref1CalcArcCenter( const VECTOR2D& aStart, const VECTOR2D& aMid, const VECTOR2D& aEnd )
  70. {
  71. VECTOR2D center;
  72. double yDelta_21 = aMid.y - aStart.y;
  73. double xDelta_21 = aMid.x - aStart.x;
  74. double yDelta_32 = aEnd.y - aMid.y;
  75. double xDelta_32 = aEnd.x - aMid.x;
  76. // This is a special case for aMid as the half-way point when aSlope = 0 and bSlope = inf
  77. // or the other way around. In that case, the center lies in a straight line between
  78. // aStart and aEnd
  79. if( ( ( xDelta_21 == 0.0 ) && ( yDelta_32 == 0.0 ) )
  80. || ( ( yDelta_21 == 0.0 ) && ( xDelta_32 == 0.0 ) ) )
  81. {
  82. center.x = ( aStart.x + aEnd.x ) / 2.0;
  83. center.y = ( aStart.y + aEnd.y ) / 2.0;
  84. return center;
  85. }
  86. // Prevent div=0 errors
  87. /*if( xDelta_21 == 0.0 )
  88. xDelta_21 = std::numeric_limits<double>::epsilon();
  89. if( xDelta_32 == 0.0 )
  90. xDelta_32 = -std::numeric_limits<double>::epsilon();*/
  91. double aSlope = yDelta_21 / xDelta_21;
  92. double bSlope = yDelta_32 / xDelta_32;
  93. if( aSlope == bSlope )
  94. {
  95. if( aStart == aEnd )
  96. {
  97. // This is a special case for a 360 degrees arc. In this case, the center is halfway between
  98. // the midpoint and either newEnd point
  99. center.x = ( aStart.x + aMid.x ) / 2.0;
  100. center.y = ( aStart.y + aMid.y ) / 2.0;
  101. return center;
  102. }
  103. else
  104. {
  105. // If the points are colinear, the center is at infinity, so offset
  106. // the slope by a minimal amount
  107. // Warning: This will induce a small error in the center location
  108. /*aSlope += std::numeric_limits<double>::epsilon();
  109. bSlope -= std::numeric_limits<double>::epsilon();*/
  110. }
  111. }
  112. center.x = ( aSlope * bSlope * ( aStart.y - aEnd.y ) + bSlope * ( aStart.x + aMid.x )
  113. - aSlope * ( aMid.x + aEnd.x ) )
  114. / ( 2 * ( bSlope - aSlope ) );
  115. if( std::abs( aSlope ) > std::abs( bSlope ) )
  116. {
  117. center.y = ( ( ( aStart.x + aMid.x ) / 2.0 - center.x ) / aSlope
  118. + ( aStart.y + aMid.y ) / 2.0 );
  119. }
  120. else
  121. {
  122. center.y =
  123. ( ( ( aMid.x + aEnd.x ) / 2.0 - center.x ) / bSlope + ( aMid.y + aEnd.y ) / 2.0 );
  124. }
  125. return center;
  126. }
  127. struct TEST_CALC_ARC_CENTER_CASE
  128. {
  129. VECTOR2I istart;
  130. VECTOR2I imid;
  131. VECTOR2I iend;
  132. };
  133. /**
  134. * Declare the test suite
  135. */
  136. BOOST_AUTO_TEST_SUITE( KiMath )
  137. BOOST_AUTO_TEST_CASE( TestCalcArcCenter3Pts )
  138. {
  139. // Order: start, mid, end
  140. std::vector<TEST_CALC_ARC_CENTER_CASE> calc_center_cases = {
  141. //{ { 183000000, 89000000 }, { 185333332, 89000000 }, { 185333333, 91333333 } } // Fails currently
  142. };
  143. const double tolerance = 1.0;
  144. // Check random points (fails currently)
  145. /*for( int i = 0; i < 100; i++ )
  146. {
  147. TEST_CALC_ARC_CENTER_CASE cas;
  148. cas.istart.x = rand();
  149. cas.istart.y = rand();
  150. cas.imid.x = rand();
  151. cas.imid.y = rand();
  152. cas.iend.x = rand();
  153. cas.iend.y = rand();
  154. calc_center_cases.push_back( cas );
  155. }*/
  156. for( const TEST_CALC_ARC_CENTER_CASE& entry : calc_center_cases )
  157. {
  158. wxString msg;
  159. VECTOR2D start( entry.istart );
  160. VECTOR2D mid( entry.imid );
  161. VECTOR2D end( entry.iend );
  162. VECTOR2D calcCenter = CalcArcCenter( start, mid, end );
  163. double crs = ( calcCenter - start ).EuclideanNorm();
  164. double crm = ( calcCenter - mid ).EuclideanNorm();
  165. double cre = ( calcCenter - end ).EuclideanNorm();
  166. double cavg = ( crs + crm + cre ) / 3.0;
  167. if( std::abs( crs - cavg ) > tolerance || std::abs( crm - cavg ) > tolerance
  168. || std::abs( cre - cavg ) > tolerance )
  169. {
  170. msg << "CalcArcCenter failed.";
  171. msg << "\nstart: " << entry.istart.Format();
  172. msg << "\nmid: " << entry.imid.Format();
  173. msg << "\nend: " << entry.iend.Format();
  174. {
  175. msg << "\nCalculated center: " << wxString::Format( "%.15f", calcCenter.x ) << ", "
  176. << wxString::Format( "%.15f", calcCenter.y );
  177. msg << "\n Avg radius: " << wxString::Format( "%.15f", cavg );
  178. msg << "\nStart radius: " << wxString::Format( "%.15f", crs );
  179. msg << "\n Mid radius: " << wxString::Format( "%.15f", crm );
  180. msg << "\n End radius: " << wxString::Format( "%.15f", cre );
  181. msg << "\n";
  182. // Check mid/end points using the calculated center (like SHAPE_ARC)
  183. EDA_ANGLE angStart( start - calcCenter );
  184. EDA_ANGLE angMid( mid - calcCenter );
  185. EDA_ANGLE angEnd( end - calcCenter );
  186. EDA_ANGLE angCenter = angEnd - angStart;
  187. VECTOR2D newMid = start;
  188. VECTOR2D newEnd = start;
  189. RotatePoint( newMid, calcCenter, -angCenter / 2.0 );
  190. RotatePoint( newEnd, calcCenter, -angCenter );
  191. msg << "\nNew mid: " << wxString::Format( "%.15f", newMid.x ) << ", "
  192. << wxString::Format( "%.15f", newMid.y );
  193. msg << "\nNew end: " << wxString::Format( "%.15f", newEnd.x ) << ", "
  194. << wxString::Format( "%.15f", newEnd.y );
  195. msg << "\n";
  196. double endsDist = ( newEnd - end ).EuclideanNorm();
  197. msg << "\nNew end is off by " << wxString::Format( "%.15f", endsDist );
  198. msg << "\n";
  199. }
  200. {
  201. VECTOR2D ref0Center;
  202. Ref0CircleCenterFrom3Points( start, mid, end, &ref0Center );
  203. double r0_rs = ( ref0Center - start ).EuclideanNorm();
  204. double r0_rm = ( ref0Center - mid ).EuclideanNorm();
  205. double r0_rre = ( ref0Center - end ).EuclideanNorm();
  206. double r0_ravg = ( r0_rs + r0_rm + r0_rre ) / 3.0;
  207. msg << "\nReference0 center: " << wxString::Format( "%.15f", ref0Center.x ) << ", "
  208. << wxString::Format( "%.15f", ref0Center.y );
  209. msg << "\nRef0 Avg radius: " << wxString::Format( "%.15f", r0_ravg );
  210. msg << "\nRef0 Start radius: " << wxString::Format( "%.15f", r0_rs );
  211. msg << "\nRef0 Mid radius: " << wxString::Format( "%.15f", r0_rm );
  212. msg << "\nRef0 End radius: " << wxString::Format( "%.15f", r0_rre );
  213. msg << "\n";
  214. }
  215. {
  216. VECTOR2D ref1Center = Ref1CalcArcCenter( start, mid, end );
  217. double r1_rs = ( ref1Center - start ).EuclideanNorm();
  218. double r1_rm = ( ref1Center - mid ).EuclideanNorm();
  219. double r1_rre = ( ref1Center - end ).EuclideanNorm();
  220. double r1_ravg = ( r1_rs + r1_rm + r1_rre ) / 3.0;
  221. msg << "\nReference1 center: " << wxString::Format( "%.15f", ref1Center.x ) << ", "
  222. << wxString::Format( "%.15f", ref1Center.y );
  223. msg << "\nRef1 Avg radius: " << wxString::Format( "%.15f", r1_ravg );
  224. msg << "\nRef1 Start radius: " << wxString::Format( "%.15f", r1_rs );
  225. msg << "\nRef1 Mid radius: " << wxString::Format( "%.15f", r1_rm );
  226. msg << "\nRef1 End radius: " << wxString::Format( "%.15f", r1_rre );
  227. msg << "\n";
  228. }
  229. BOOST_CHECK_MESSAGE( false, msg );
  230. }
  231. }
  232. }
  233. BOOST_AUTO_TEST_CASE( TestInterceptsPositiveX )
  234. {
  235. BOOST_CHECK( !InterceptsPositiveX( 10.0, 20.0 ) );
  236. BOOST_CHECK( !InterceptsPositiveX( 10.0, 120.0 ) );
  237. BOOST_CHECK( !InterceptsPositiveX( 10.0, 220.0 ) );
  238. BOOST_CHECK( !InterceptsPositiveX( 10.0, 320.0 ) );
  239. BOOST_CHECK( InterceptsPositiveX( 20.0, 10.0 ) );
  240. BOOST_CHECK( InterceptsPositiveX( 345.0, 15.0 ) );
  241. }
  242. BOOST_AUTO_TEST_CASE( TestInterceptsNegativeX )
  243. {
  244. BOOST_CHECK( !InterceptsNegativeX( 10.0, 20.0 ) );
  245. BOOST_CHECK( !InterceptsNegativeX( 10.0, 120.0 ) );
  246. BOOST_CHECK( InterceptsNegativeX( 10.0, 220.0 ) );
  247. BOOST_CHECK( InterceptsNegativeX( 10.0, 320.0 ) );
  248. BOOST_CHECK( InterceptsNegativeX( 20.0, 10.0 ) );
  249. BOOST_CHECK( !InterceptsNegativeX( 345.0, 15.0 ) );
  250. BOOST_CHECK( InterceptsNegativeX( 145.0, 225.0 ) );
  251. }
  252. BOOST_AUTO_TEST_CASE( TestHash128 )
  253. {
  254. const HASH_128 zero;
  255. for( size_t i = 0; i < 16; i++ )
  256. BOOST_CHECK( zero.Value8[i] == 0 );
  257. HASH_128* zero_h = new HASH_128;
  258. for( size_t i = 0; i < 16; i++ )
  259. BOOST_CHECK( zero_h->Value8[i] == 0 );
  260. delete zero_h;
  261. HASH_128 h;
  262. h.Value64[0] = 0x00CDEF0123456789ULL;
  263. h.Value64[1] = 0x56789ABCDEF01234ULL;
  264. BOOST_CHECK( h != zero );
  265. HASH_128 b = h;
  266. BOOST_CHECK( b != zero );
  267. BOOST_CHECK( b == h );
  268. BOOST_CHECK( h == b );
  269. char hashStr[33] = {};
  270. snprintf( hashStr, sizeof( hashStr ), "%016llX%016llX", h.Value64[0], h.Value64[1] );
  271. BOOST_CHECK( std::string( hashStr ) == std::string( "00CDEF012345678956789ABCDEF01234" ) );
  272. }
  273. //-----------------------------------------------------------------------------
  274. // Test MMH3_HASH against the original MurmurHash3_x64_128 implementation
  275. #include <mmh3_hash.h>
  276. void MurmurHash3_x64_128( const void* key, const int len, const uint32_t seed, void* out );
  277. BOOST_AUTO_TEST_CASE( TestMMH3Hash )
  278. {
  279. std::vector<std::vector<int32_t>> data;
  280. for( size_t i = 0; i < 10; i++ )
  281. {
  282. std::vector<int32_t>& vec = data.emplace_back();
  283. size_t vecSize = rand() % 128;
  284. for( size_t j = 0; j < vecSize; j++ )
  285. vec.emplace_back( rand() );
  286. }
  287. int64_t seed = 0; // Test 0 seed first
  288. for( const std::vector<int32_t>& vec : data )
  289. {
  290. MMH3_HASH mmh3( seed );
  291. for( const int32_t val : vec )
  292. mmh3.add( val );
  293. HASH_128 h128 = mmh3.digest();
  294. HASH_128 orig128;
  295. MurmurHash3_x64_128( (void*) vec.data(), vec.size() * sizeof( vec[0] ), seed,
  296. (void*) orig128.Value64 );
  297. BOOST_CHECK( h128 == orig128 );
  298. // Generate new random seed
  299. seed = rand();
  300. }
  301. }
  302. //-----------------------------------------------------------------------------
  303. // The original MurmurHash3_x64_128 implementation
  304. // Microsoft Visual Studio
  305. #if defined( _MSC_VER )
  306. #define FORCE_INLINE __forceinline
  307. #include <stdlib.h>
  308. #define ROTL64( x, y ) _rotl64( x, y )
  309. #define BIG_CONSTANT( x ) ( x )
  310. // Other compilers
  311. #else // defined(_MSC_VER)
  312. #define FORCE_INLINE inline __attribute__( ( always_inline ) )
  313. inline uint64_t rotl64( uint64_t x, int8_t r )
  314. {
  315. return ( x << r ) | ( x >> ( 64 - r ) );
  316. }
  317. #define ROTL64( x, y ) rotl64( x, y )
  318. #define BIG_CONSTANT( x ) ( x##LLU )
  319. #endif // !defined(_MSC_VER)
  320. FORCE_INLINE uint64_t getblock64 ( const uint64_t * p, int i )
  321. {
  322. return p[i];
  323. }
  324. FORCE_INLINE uint64_t fmix64 ( uint64_t k )
  325. {
  326. k ^= k >> 33;
  327. k *= BIG_CONSTANT(0xff51afd7ed558ccd);
  328. k ^= k >> 33;
  329. k *= BIG_CONSTANT(0xc4ceb9fe1a85ec53);
  330. k ^= k >> 33;
  331. return k;
  332. }
  333. void MurmurHash3_x64_128 ( const void * key, const int len,
  334. const uint32_t seed, void * out )
  335. {
  336. const uint8_t * data = (const uint8_t*)key;
  337. const int nblocks = len / 16;
  338. uint64_t h1 = seed;
  339. uint64_t h2 = seed;
  340. const uint64_t c1 = BIG_CONSTANT(0x87c37b91114253d5);
  341. const uint64_t c2 = BIG_CONSTANT(0x4cf5ad432745937f);
  342. //----------
  343. // body
  344. const uint64_t * blocks = (const uint64_t *)(data);
  345. for(int i = 0; i < nblocks; i++)
  346. {
  347. uint64_t k1 = getblock64(blocks,i*2+0);
  348. uint64_t k2 = getblock64(blocks,i*2+1);
  349. k1 *= c1; k1 = ROTL64(k1,31); k1 *= c2; h1 ^= k1;
  350. h1 = ROTL64(h1,27); h1 += h2; h1 = h1*5+0x52dce729;
  351. k2 *= c2; k2 = ROTL64(k2,33); k2 *= c1; h2 ^= k2;
  352. h2 = ROTL64(h2,31); h2 += h1; h2 = h2*5+0x38495ab5;
  353. }
  354. //----------
  355. // tail
  356. const uint8_t * tail = (const uint8_t*)(data + nblocks*16);
  357. uint64_t k1 = 0;
  358. uint64_t k2 = 0;
  359. switch(len & 15)
  360. {
  361. case 15: k2 ^= ((uint64_t)tail[14]) << 48;
  362. case 14: k2 ^= ((uint64_t)tail[13]) << 40;
  363. case 13: k2 ^= ((uint64_t)tail[12]) << 32;
  364. case 12: k2 ^= ((uint64_t)tail[11]) << 24;
  365. case 11: k2 ^= ((uint64_t)tail[10]) << 16;
  366. case 10: k2 ^= ((uint64_t)tail[ 9]) << 8;
  367. case 9: k2 ^= ((uint64_t)tail[ 8]) << 0;
  368. k2 *= c2; k2 = ROTL64(k2,33); k2 *= c1; h2 ^= k2;
  369. case 8: k1 ^= ((uint64_t)tail[ 7]) << 56;
  370. case 7: k1 ^= ((uint64_t)tail[ 6]) << 48;
  371. case 6: k1 ^= ((uint64_t)tail[ 5]) << 40;
  372. case 5: k1 ^= ((uint64_t)tail[ 4]) << 32;
  373. case 4: k1 ^= ((uint64_t)tail[ 3]) << 24;
  374. case 3: k1 ^= ((uint64_t)tail[ 2]) << 16;
  375. case 2: k1 ^= ((uint64_t)tail[ 1]) << 8;
  376. case 1: k1 ^= ((uint64_t)tail[ 0]) << 0;
  377. k1 *= c1; k1 = ROTL64(k1,31); k1 *= c2; h1 ^= k1;
  378. };
  379. //----------
  380. // finalization
  381. h1 ^= len; h2 ^= len;
  382. h1 += h2;
  383. h2 += h1;
  384. h1 = fmix64(h1);
  385. h2 = fmix64(h2);
  386. h1 += h2;
  387. h2 += h1;
  388. ((uint64_t*)out)[0] = h1;
  389. ((uint64_t*)out)[1] = h2;
  390. }
  391. //-----------------------------------------------------------------------------
  392. #undef FORCE_INLINE
  393. #undef ROTL64
  394. #undef BIG_CONSTANT
  395. BOOST_AUTO_TEST_SUITE_END()