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.

352 lines
8.7 KiB

  1. /*
  2. * This program source code file is part of KiCad, a free EDA CAD application.
  3. *
  4. * Copyright (C) 2021 Roberto Fernandez Bautista <roberto.fer.bau@gmail.com>
  5. * Copyright (C) 2021 KiCad Developers, see AUTHORS.txt for contributors.
  6. *
  7. * This program is free software: you can redistribute it and/or modify it
  8. * under the terms of the GNU General Public License as published by the
  9. * Free Software Foundation, either version 3 of the License, or (at your
  10. * option) any later version.
  11. *
  12. * This program is distributed in the hope that it will be useful, but
  13. * WITHOUT ANY WARRANTY; without even the implied warranty of
  14. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  15. * General Public License for more details.
  16. *
  17. * You should have received a copy of the GNU General Public License along
  18. * with this program. If not, see <http://www.gnu.org/licenses/>.
  19. */
  20. #include <unit_test_utils/unit_test_utils.h>
  21. #include <geometry/circle.h>
  22. #include <geometry/seg.h> // for SEG
  23. #include <geometry/shape.h> // for MIN_PRECISION_IU
  24. bool CompareVector2I( const VECTOR2I& aVecA, const VECTOR2I& aVecB )
  25. {
  26. if( aVecA.x > ( aVecB.x + SHAPE::MIN_PRECISION_IU ) )
  27. return false;
  28. else if( aVecA.x < ( aVecB.x - SHAPE::MIN_PRECISION_IU ) )
  29. return false;
  30. else if( aVecA.y > ( aVecB.y + SHAPE::MIN_PRECISION_IU ) )
  31. return false;
  32. else if( aVecA.y < ( aVecB.y - SHAPE::MIN_PRECISION_IU ) )
  33. return false;
  34. else
  35. return true;
  36. }
  37. BOOST_AUTO_TEST_SUITE( Circle )
  38. /**
  39. * Checks whether the construction of a circle referencing external parameters works
  40. * and that the parameters can be modified directly.
  41. */
  42. BOOST_AUTO_TEST_CASE( ParameterCtorMod )
  43. {
  44. const VECTOR2I center( 10, 20 );
  45. const int radius = 10;
  46. // Build a circle referencing the previous values
  47. CIRCLE circle( center, radius );
  48. BOOST_CHECK_EQUAL( circle.Center, VECTOR2I( 10, 20 ) );
  49. BOOST_CHECK_EQUAL( circle.Radius, 10 );
  50. // Modify the parameters
  51. circle.Center += VECTOR2I( 10, 10 );
  52. circle.Radius += 20;
  53. // Check the parameters were modified
  54. BOOST_CHECK_EQUAL( circle.Center, VECTOR2I( 20, 30 ) );
  55. BOOST_CHECK_EQUAL( circle.Radius, 30 );
  56. }
  57. /**
  58. * Struct to hold test cases for a given circle, a point and an expected return point
  59. */
  60. struct CIR_PT_PT_CASE
  61. {
  62. std::string m_case_name;
  63. CIRCLE m_circle;
  64. VECTOR2I m_point;
  65. VECTOR2I m_exp_result;
  66. };
  67. // clang-format off
  68. /**
  69. * Test cases for #CIRCLE::NearestPoint
  70. */
  71. static const std::vector<CIR_PT_PT_CASE> nearest_point_cases = {
  72. {
  73. "on center",
  74. { { 10, 10 }, 20 },
  75. { 10, 10 },
  76. { 30, 10 }, // special case: when at the circle return a point on the x axis
  77. },
  78. {
  79. "inside",
  80. { { 10, 10 }, 20 },
  81. { 10, 20 },
  82. { 10, 30 },
  83. },
  84. {
  85. "outside",
  86. { { 10, 10 }, 20 },
  87. { 10, 50 },
  88. { 10, 30 },
  89. },
  90. {
  91. "angled",
  92. { { 10, 10 }, 20 },
  93. { 50, 50 },
  94. { 24, 24 },
  95. },
  96. };
  97. // clang-format on
  98. BOOST_AUTO_TEST_CASE( NearestPoint )
  99. {
  100. for( const auto& c : nearest_point_cases )
  101. {
  102. BOOST_TEST_CONTEXT( c.m_case_name )
  103. {
  104. VECTOR2I ret = c.m_circle.NearestPoint( c.m_point );
  105. BOOST_CHECK_EQUAL( ret, c.m_exp_result );
  106. }
  107. }
  108. }
  109. /**
  110. * Struct to hold test cases for two circles, and an vector of points
  111. */
  112. struct CIR_CIR_VECPT_CASE
  113. {
  114. std::string m_case_name;
  115. CIRCLE m_circle1;
  116. CIRCLE m_circle2;
  117. std::vector<VECTOR2I> m_exp_result;
  118. };
  119. // clang-format off
  120. /**
  121. * Test cases for #CIRCLE::Intersect( const CIRCLE& aCircle )
  122. */
  123. static const std::vector<CIR_CIR_VECPT_CASE> intersect_circle_cases = {
  124. {
  125. "two point aligned",
  126. { { 10, 10 }, 20 },
  127. { { 10, 45 }, 20 },
  128. {
  129. { 0, 27 },
  130. { 21, 27 },
  131. },
  132. },
  133. {
  134. "two point angled",
  135. { { 10, 10 }, 20 },
  136. { { 20, 20 }, 20 },
  137. {
  138. { 2, 28 },
  139. { 28, 2 },
  140. },
  141. },
  142. {
  143. "tangent aligned",
  144. { { 10, 10 }, 20 },
  145. { { 10, 50 }, 20 },
  146. {
  147. { 10, 30 },
  148. },
  149. },
  150. {
  151. "no intersection",
  152. { { 10, 10 }, 20 },
  153. { { 10, 51 }, 20 },
  154. {
  155. //no points
  156. },
  157. },
  158. };
  159. // clang-format on
  160. BOOST_AUTO_TEST_CASE( IntersectCircle )
  161. {
  162. for( const auto& c : intersect_circle_cases )
  163. {
  164. BOOST_TEST_CONTEXT( c.m_case_name + " Case 1" )
  165. {
  166. std::vector<VECTOR2I> ret1 = c.m_circle1.Intersect( c.m_circle2 );
  167. BOOST_CHECK_EQUAL( c.m_exp_result.size(), ret1.size() );
  168. KI_TEST::CheckUnorderedMatches( c.m_exp_result, ret1, CompareVector2I );
  169. }
  170. BOOST_TEST_CONTEXT( c.m_case_name + " Case 2" )
  171. {
  172. // Test the other direction
  173. std::vector<VECTOR2I> ret2 = c.m_circle2.Intersect( c.m_circle1 );
  174. BOOST_CHECK_EQUAL( c.m_exp_result.size(), ret2.size() );
  175. KI_TEST::CheckUnorderedMatches( c.m_exp_result, ret2, CompareVector2I );
  176. }
  177. }
  178. }
  179. /**
  180. * Struct to hold test cases for a circle, a line and an expected vector of points
  181. */
  182. struct SEG_SEG_VECPT_CASE
  183. {
  184. std::string m_case_name;
  185. CIRCLE m_circle;
  186. SEG m_seg;
  187. std::vector<VECTOR2I> m_exp_result;
  188. };
  189. // clang-format off
  190. /**
  191. * Test cases for #CIRCLE::Intersect( const SEG& aSeg )
  192. */
  193. static const std::vector<SEG_SEG_VECPT_CASE> intersect_line_cases = {
  194. {
  195. "two point aligned",
  196. { { 0, 0 }, 20 },
  197. { { 10, 45 }, {10, 40} },
  198. {
  199. { 10, -17 },
  200. { 10, 17 },
  201. },
  202. },
  203. {
  204. "two point angled",
  205. { { 0, 0 }, 20 },
  206. { { -20, -40 }, {20, 40} },
  207. {
  208. { 8, 17 },
  209. { -8, -17 },
  210. },
  211. },
  212. {
  213. "tangent",
  214. { { 0, 0 }, 20 },
  215. { { 20, 0 }, {20, 40} },
  216. {
  217. { 20, 0 }
  218. },
  219. },
  220. {
  221. "no intersection",
  222. { { 0, 0 }, 20 },
  223. { { 25, 0 }, {25, 40} },
  224. {
  225. //no points
  226. },
  227. },
  228. };
  229. // clang-format on
  230. BOOST_AUTO_TEST_CASE( IntersectLine )
  231. {
  232. for( const auto& c : intersect_line_cases )
  233. {
  234. BOOST_TEST_CONTEXT( c.m_case_name )
  235. {
  236. std::vector<VECTOR2I> ret = c.m_circle.Intersect( c.m_seg );
  237. BOOST_CHECK_EQUAL( c.m_exp_result.size(), ret.size() );
  238. KI_TEST::CheckUnorderedMatches( c.m_exp_result, ret, CompareVector2I );
  239. }
  240. }
  241. }
  242. /**
  243. * Struct to hold test cases for two lines, a point and an expected returned circle
  244. */
  245. struct CIR_SEG_VECPT_CASE
  246. {
  247. std::string m_case_name;
  248. SEG m_segA;
  249. SEG m_segB;
  250. VECTOR2I m_pt;
  251. CIRCLE m_exp_result;
  252. };
  253. // clang-format off
  254. /**
  255. * Test cases for #CIRCLE::Intersect( const SEG& aSeg )
  256. */
  257. static const std::vector<CIR_SEG_VECPT_CASE> construct_tan_tan_pt_cases = {
  258. {
  259. "90 degree segs, point on seg",
  260. { { 0, 0 }, { 0, 1000 } },
  261. { { 0, 0 }, { 1000, 0 } },
  262. { 0, 400 },
  263. { { 400, 400} , 400 },
  264. },
  265. {
  266. "90 degree segs, point floating",
  267. { { 0, 0 }, { 0, 1000 } },
  268. { { 0, 0 }, { 1000, 0 } },
  269. { 400, 400 },
  270. { { 1356, 1356} , 1356 },
  271. },
  272. {
  273. "45 degree segs, point on seg",
  274. { { 0, 0 }, { 1000, 0 } },
  275. { { 0, 0 }, { 1000, 1000 } },
  276. { 400, 0 },
  277. { { 400, 166} , 165 },
  278. },
  279. {
  280. "45 degree segs, point floating",
  281. { { 0, 0 }, { 1000, 0 } },
  282. { { 0, 0 }, { 1000, 1000 } },
  283. { 200, 100 },
  284. { { 331, 137} , 137 },
  285. },
  286. {
  287. "135 degree segs, point on seg",
  288. { { 0, 0 }, { 1000, 0 } },
  289. { { 0, 0 }, { -1000, 1000 } },
  290. { 400, 0 },
  291. { { 394, 950} , 950 },
  292. },
  293. {
  294. "135 degree segs, point floating",
  295. { { 0, 0 }, { 1000, 0 } },
  296. { { 0, 0 }, { -1000, 1000 } },
  297. { 200, 100 },
  298. { { 814, 1964} , 1964 },
  299. },
  300. {
  301. "point on intersection",
  302. { { 10, 0 }, { 1000, 0 } },
  303. { { 10, 0 }, { -1000, 1000 } },
  304. { 10, 0 },
  305. { { 10, 0} , 0 }, // special case: radius=0
  306. },
  307. };
  308. // clang-format on
  309. BOOST_AUTO_TEST_CASE( ConstructFromTanTanPt )
  310. {
  311. for( const auto& c : construct_tan_tan_pt_cases )
  312. {
  313. BOOST_TEST_CONTEXT( c.m_case_name )
  314. {
  315. CIRCLE circle;
  316. circle.ConstructFromTanTanPt( c.m_segA, c.m_segB, c.m_pt );
  317. BOOST_CHECK_EQUAL( c.m_exp_result.Center, circle.Center );
  318. BOOST_CHECK_EQUAL( c.m_exp_result.Radius, circle.Radius );
  319. }
  320. }
  321. }
  322. BOOST_AUTO_TEST_SUITE_END()