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.

698 lines
17 KiB

  1. /*
  2. * This program source code file is part of KiCad, a free EDA CAD application.
  3. *
  4. * Copyright (C) 2017 CERN
  5. * @author Alejandro García Montoro <alejandro.garciamontoro@gmail.com>
  6. * Copyright (C) 2019-2021 KiCad Developers, see AUTHORS.txt for contributors.
  7. *
  8. * This program is free software; you can redistribute it and/or
  9. * modify it under the terms of the GNU General Public License
  10. * as published by the Free Software Foundation; either version 2
  11. * of the License, or (at your option) any later version.
  12. *
  13. * This program is distributed in the hope that it will be useful,
  14. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  15. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  16. * GNU General Public License for more details.
  17. *
  18. * You should have received a copy of the GNU General Public License
  19. * along with this program; if not, you may find one here:
  20. * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
  21. * or you may search the http://www.gnu.org website for the version 2 license,
  22. * or you may write to the Free Software Foundation, Inc.,
  23. * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
  24. */
  25. #include <unit_test_utils/unit_test_utils.h>
  26. #include <geometry/seg.h>
  27. /**
  28. * Predicate to check expected collision between two segments
  29. * @param aSegA the first #SEG
  30. * @param aSegB the second #SEG
  31. * @param aClearance the collision clearance
  32. * @param aExp expected collision
  33. * @return does the distance calculated agree?
  34. */
  35. bool SegCollideCorrect( const SEG& aSegA, const SEG& aSegB, int aClearance, bool aExp )
  36. {
  37. const bool AtoB = aSegA.Collide( aSegB, aClearance );
  38. const bool BtoA = aSegB.Collide( aSegA, aClearance );
  39. const bool ok = ( AtoB == aExp ) && ( BtoA == aExp );
  40. if( AtoB != BtoA )
  41. {
  42. std::stringstream ss;
  43. ss << "Segment collision is not the same in both directions: expected " << aExp << ", got "
  44. << AtoB << " & " << BtoA;
  45. BOOST_TEST_INFO( ss.str() );
  46. }
  47. else if( !ok )
  48. {
  49. std::stringstream ss;
  50. ss << "Collision incorrect: expected " << aExp << ", got " << AtoB;
  51. BOOST_TEST_INFO( ss.str() );
  52. }
  53. return ok;
  54. }
  55. /**
  56. * Predicate to check expected distance between two segments
  57. * @param aSegA the first #SEG
  58. * @param aSegB the second #SEG
  59. * @param aExp expected distance
  60. * @return does the distance calculated agree?
  61. */
  62. bool SegDistanceCorrect( const SEG& aSegA, const SEG& aSegB, int aExp )
  63. {
  64. const int AtoB = aSegA.Distance( aSegB );
  65. const int BtoA = aSegB.Distance( aSegA );
  66. bool ok = ( AtoB == aExp ) && ( BtoA == aExp );
  67. if( AtoB != BtoA )
  68. {
  69. std::stringstream ss;
  70. ss << "Segment distance is not the same in both directions: expected " << aExp << ", got "
  71. << AtoB << " & " << BtoA;
  72. BOOST_TEST_INFO( ss.str() );
  73. }
  74. else if( !ok )
  75. {
  76. std::stringstream ss;
  77. ss << "Distance incorrect: expected " << aExp << ", got " << AtoB;
  78. BOOST_TEST_INFO( ss.str() );
  79. }
  80. // Sanity check: the collision should be consistent with the distance
  81. ok = ok && SegCollideCorrect( aSegA, aSegB, 0, aExp == 0 );
  82. return ok;
  83. }
  84. /**
  85. * Predicate to check expected distance between a segment and a point
  86. * @param aSegA the segment
  87. * @param aVec the vector (point)
  88. * @param aExp expected distance
  89. * @return does the distance calculated agree?
  90. */
  91. bool SegVecDistanceCorrect( const SEG& aSeg, const VECTOR2I& aVec, int aExp )
  92. {
  93. const int dist = aSeg.Distance( aVec );
  94. bool ok = ( dist == aExp );
  95. if( !ok )
  96. {
  97. std::stringstream ss;
  98. ss << "Distance incorrect: expected " << aExp << ", got " << dist;
  99. BOOST_TEST_INFO( ss.str() );
  100. }
  101. return ok;
  102. }
  103. /**
  104. * Predicate to check expected collision between two segments
  105. * @param aSegA the first #SEG
  106. * @param sSegB the second #SEG
  107. * @param aExp expected collinearity
  108. * @return does the collinearity calculated agree?
  109. */
  110. bool SegCollinearCorrect( const SEG& aSegA, const SEG& aSegB, bool aExp )
  111. {
  112. const bool AtoB = aSegA.Collinear( aSegB );
  113. const bool BtoA = aSegB.Collinear( aSegA );
  114. const bool ok = ( AtoB == aExp ) && ( BtoA == aExp );
  115. if( AtoB != BtoA )
  116. {
  117. std::stringstream ss;
  118. ss << "Segment collinearity is not the same in both directions: expected " << aExp
  119. << ", got " << AtoB << " & " << BtoA;
  120. BOOST_TEST_INFO( ss.str() );
  121. }
  122. else if( !ok )
  123. {
  124. std::stringstream ss;
  125. ss << "Collinearity incorrect: expected " << aExp << ", got " << AtoB;
  126. BOOST_TEST_INFO( ss.str() );
  127. }
  128. return ok;
  129. }
  130. /**
  131. * Predicate to check expected parallelism between two segments
  132. * @param aSegA the first #SEG
  133. * @param sSegB the second #SEG
  134. * @param aExp expected parallelism: true = segments are parallel
  135. * false = segments are not parallel
  136. * @return does the parallelism calculated agree?
  137. */
  138. bool SegParallelCorrect( const SEG& aSegA, const SEG& aSegB, bool aExp )
  139. {
  140. const bool AtoB = aSegA.ApproxParallel( aSegB );
  141. const bool BtoA = aSegB.ApproxParallel( aSegA );
  142. const bool ok = ( AtoB == aExp ) && ( BtoA == aExp );
  143. if( AtoB != BtoA )
  144. {
  145. std::stringstream ss;
  146. ss << "Segment parallelism is not the same in both directions: expected " << aExp
  147. << ", got AtoB: " << AtoB << " BtoA:" << BtoA;
  148. BOOST_TEST_INFO( ss.str() );
  149. }
  150. else if( !ok )
  151. {
  152. std::stringstream ss;
  153. ss << "Parallelism incorrect: expected " << aExp << ", got " << AtoB;
  154. BOOST_TEST_INFO( ss.str() );
  155. }
  156. return ok;
  157. }
  158. /**
  159. * Predicate to check expected perpendicularity between two segments
  160. * @param aSegA the first #SEG
  161. * @param sSegB the second #SEG
  162. * @param aExp expected perpendicularity: true = segments are perpendicular
  163. * false = segments are not perpendicular
  164. * @return does the perpendicularity calculated agree?
  165. */
  166. bool SegPerpendicularCorrect( const SEG& aSegA, const SEG& aSegB, bool aExp )
  167. {
  168. const bool AtoB = aSegA.ApproxPerpendicular( aSegB );
  169. const bool BtoA = aSegB.ApproxPerpendicular( aSegA );
  170. const bool ok = ( AtoB == aExp ) && ( BtoA == aExp );
  171. if( AtoB != BtoA )
  172. {
  173. std::stringstream ss;
  174. ss << "Segment perpendicularity is not the same in both directions: expected " << aExp
  175. << ", got AtoB: " << AtoB << " BtoA:" << BtoA;
  176. BOOST_TEST_INFO( ss.str() );
  177. }
  178. else if( !ok )
  179. {
  180. std::stringstream ss;
  181. ss << "Perpendicularity incorrect: expected " << aExp << ", got " << AtoB;
  182. BOOST_TEST_INFO( ss.str() );
  183. }
  184. return ok;
  185. }
  186. BOOST_AUTO_TEST_SUITE( Segment )
  187. /**
  188. * Checks whether the construction of a segment referencing external points works
  189. * and that the endpoints can be modified as normal points.
  190. */
  191. BOOST_AUTO_TEST_CASE( EndpointCtorMod )
  192. {
  193. const VECTOR2I pointA{ 10, 20 };
  194. const VECTOR2I pointB{ 100, 200 };
  195. // Build a segment referencing the previous points
  196. SEG segment( pointA, pointB );
  197. BOOST_CHECK_EQUAL( pointA, VECTOR2I( 10, 20 ) );
  198. BOOST_CHECK_EQUAL( pointB, VECTOR2I( 100, 200 ) );
  199. // Modify the ends of the segments
  200. segment.A += VECTOR2I( 10, 10 );
  201. segment.B += VECTOR2I( 100, 100 );
  202. // Check that the ends in segment are modified
  203. BOOST_CHECK_EQUAL( segment.A, VECTOR2I( 20, 30 ) );
  204. BOOST_CHECK_EQUAL( segment.B, VECTOR2I( 200, 300 ) );
  205. }
  206. struct SEG_SEG_DISTANCE_CASE
  207. {
  208. std::string m_case_name;
  209. SEG m_seg_a;
  210. SEG m_seg_b;
  211. int m_exp_dist;
  212. };
  213. // clang-format off
  214. static const std::vector<SEG_SEG_DISTANCE_CASE> seg_seg_dist_cases = {
  215. {
  216. "Parallel, 10 apart",
  217. { { 0, 0 }, { 10, 0 } },
  218. { { 0, 10 }, { 10, 10 } },
  219. 10,
  220. },
  221. {
  222. "Non-parallel, 10 apart",
  223. { { 0, -5 }, { 10, 0 } },
  224. { { 0, 10 }, { 10, 10 } },
  225. 10,
  226. },
  227. {
  228. "Co-incident",
  229. { { 0, 0 }, { 30, 0 } },
  230. { { 10, 0 }, { 20, 0 } },
  231. 0,
  232. },
  233. {
  234. "Crossing",
  235. { { 0, -10 }, { 0, 10 } },
  236. { { -20, 0 }, { 20, 0 } },
  237. 0,
  238. },
  239. {
  240. "T-junction",
  241. { { 0, -10 }, { 0, 10 } },
  242. { { -20, 0 }, { 0, 0 } },
  243. 0,
  244. },
  245. {
  246. "T-junction (no touch)",
  247. { { 0, -10 }, { 0, 10 } },
  248. { { -20, 0 }, { -2, 0 } },
  249. 2,
  250. },
  251. };
  252. // clang-format on
  253. BOOST_AUTO_TEST_CASE( SegSegDistance )
  254. {
  255. for( const auto& c : seg_seg_dist_cases )
  256. {
  257. BOOST_TEST_CONTEXT( c.m_case_name )
  258. {
  259. BOOST_CHECK_PREDICATE( SegDistanceCorrect, ( c.m_seg_a )( c.m_seg_b )( c.m_exp_dist ) );
  260. }
  261. }
  262. }
  263. struct SEG_VECTOR_DISTANCE_CASE
  264. {
  265. std::string m_case_name;
  266. SEG m_seg;
  267. VECTOR2I m_vec;
  268. int m_exp_dist;
  269. };
  270. // clang-format off
  271. static const std::vector<SEG_VECTOR_DISTANCE_CASE> seg_vec_dist_cases = {
  272. {
  273. "On endpoint",
  274. { { 0, 0 }, { 10, 0 } },
  275. { 0, 0 },
  276. 0,
  277. },
  278. {
  279. "On segment",
  280. { { 0, 0 }, { 10, 0 } },
  281. { 3, 0 },
  282. 0,
  283. },
  284. {
  285. "At side",
  286. { { 0, 0 }, { 10, 0 } },
  287. { 3, 2 },
  288. 2,
  289. },
  290. {
  291. "At end (collinear)",
  292. { { 0, 0 }, { 10, 0 } },
  293. { 12, 0 },
  294. 2,
  295. },
  296. {
  297. "At end (not collinear)",
  298. { { 0, 0 }, { 1000, 0 } },
  299. { 1000 + 200, 200 },
  300. 282, // sqrt(200^2 + 200^2), rounded down
  301. },
  302. };
  303. // clang-format on
  304. BOOST_AUTO_TEST_CASE( SegVecDistance )
  305. {
  306. for( const auto& c : seg_vec_dist_cases )
  307. {
  308. BOOST_TEST_CONTEXT( c.m_case_name )
  309. {
  310. BOOST_CHECK_PREDICATE( SegVecDistanceCorrect, ( c.m_seg )( c.m_vec )( c.m_exp_dist ) );
  311. }
  312. }
  313. }
  314. /**
  315. * Test cases for collisions (with clearance, for no clearance,
  316. * it's just a SEG_SEG_DISTANCE_CASE of 0)
  317. */
  318. struct SEG_SEG_COLLIDE_CASE
  319. {
  320. std::string m_case_name;
  321. SEG m_seg_a;
  322. SEG m_seg_b;
  323. int m_clearance;
  324. bool m_exp_coll;
  325. };
  326. // clang-format off
  327. static const std::vector<SEG_SEG_COLLIDE_CASE> seg_seg_coll_cases = {
  328. {
  329. "Parallel, 10 apart, 5 clear",
  330. { { 0, 0 }, { 10, 0 } },
  331. { { 0, 10 }, { 10, 10 } },
  332. 5,
  333. false,
  334. },
  335. {
  336. "Parallel, 10 apart, 10 clear",
  337. { { 0, 0 }, { 10, 0 } },
  338. { { 0, 10 }, { 10, 10 } },
  339. 10,
  340. false,
  341. },
  342. {
  343. "Parallel, 10 apart, 11 clear",
  344. { { 0, 0 }, { 10, 0 } },
  345. { { 0, 10 }, { 10, 10 } },
  346. 11,
  347. true,
  348. },
  349. {
  350. "T-junction, 2 apart, 2 clear",
  351. { { 0, -10 }, { 0, 0 } },
  352. { { -20, 0 }, { -2, 0 } },
  353. 2,
  354. false,
  355. },
  356. {
  357. "T-junction, 2 apart, 3 clear",
  358. { { 0, -10 }, { 0, 0 } },
  359. { { -20, 0 }, { -2, 0 } },
  360. 3,
  361. true,
  362. },
  363. };
  364. // clang-format on
  365. BOOST_AUTO_TEST_CASE( SegSegCollision )
  366. {
  367. for( const auto& c : seg_seg_coll_cases )
  368. {
  369. BOOST_TEST_CONTEXT( c.m_case_name )
  370. {
  371. BOOST_CHECK_PREDICATE( SegCollideCorrect,
  372. ( c.m_seg_a )( c.m_seg_b )( c.m_clearance )( c.m_exp_coll ) );
  373. }
  374. }
  375. }
  376. /**
  377. * Struct to hold general cases for collinearity, paralellism and perpendicularity
  378. */
  379. struct SEG_SEG_BOOLEAN_CASE
  380. {
  381. std::string m_case_name;
  382. SEG m_seg_a;
  383. SEG m_seg_b;
  384. bool m_exp_result;
  385. };
  386. // clang-format off
  387. /**
  388. * Test cases for collinearity
  389. */
  390. static const std::vector<SEG_SEG_BOOLEAN_CASE> seg_vec_collinear_cases = {
  391. {
  392. "coincident",
  393. { { 0, 0 }, { 10, 0 } },
  394. { { 0, 0 }, { 10, 0 } },
  395. true,
  396. },
  397. {
  398. "end-to-end",
  399. { { 0, 0 }, { 10, 0 } },
  400. { { 10, 0 }, { 20, 0 } },
  401. true,
  402. },
  403. {
  404. "In segment",
  405. { { 0, 0 }, { 10, 0 } },
  406. { { 4, 0 }, { 7, 0 } },
  407. true,
  408. },
  409. {
  410. "At side, parallel",
  411. { { 0, 0 }, { 10, 0 } },
  412. { { 4, 1 }, { 7, 1 } },
  413. false,
  414. },
  415. {
  416. "crossing",
  417. { { 0, 0 }, { 10, 0 } },
  418. { { 5, -5 }, { 5, 5 } },
  419. false,
  420. },
  421. };
  422. // clang-format on
  423. BOOST_AUTO_TEST_CASE( SegSegCollinear )
  424. {
  425. for( const auto& c : seg_vec_collinear_cases )
  426. {
  427. BOOST_TEST_CONTEXT( c.m_case_name )
  428. {
  429. BOOST_CHECK_PREDICATE( SegCollinearCorrect,
  430. ( c.m_seg_a )( c.m_seg_b )( c.m_exp_result ) );
  431. }
  432. }
  433. }
  434. // clang-format off
  435. /**
  436. * Test cases for paralellism
  437. */
  438. static const std::vector<SEG_SEG_BOOLEAN_CASE> seg_vec_parallel_cases = {
  439. {
  440. "coincident",
  441. { { 0, 0 }, { 10, 0 } },
  442. { { 0, 0 }, { 10, 0 } },
  443. true,
  444. },
  445. {
  446. "end-to-end",
  447. { { 0, 0 }, { 10, 0 } },
  448. { { 10, 0 }, { 20, 0 } },
  449. true,
  450. },
  451. {
  452. "In segment",
  453. { { 0, 0 }, { 10, 0 } },
  454. { { 4, 0 }, { 7, 0 } },
  455. true,
  456. },
  457. {
  458. "At side, parallel",
  459. { { 0, 0 }, { 10, 0 } },
  460. { { 4, 1 }, { 7, 1 } },
  461. true,
  462. },
  463. {
  464. "crossing",
  465. { { 0, 0 }, { 10, 0 } },
  466. { { 5, -5 }, { 5, 5 } },
  467. false,
  468. },
  469. };
  470. // clang-format on
  471. BOOST_AUTO_TEST_CASE( SegSegParallel )
  472. {
  473. for( const auto& c : seg_vec_parallel_cases )
  474. {
  475. BOOST_TEST_CONTEXT( c.m_case_name )
  476. {
  477. BOOST_CHECK_PREDICATE( SegParallelCorrect,
  478. ( c.m_seg_a )( c.m_seg_b )( c.m_exp_result ) );
  479. }
  480. }
  481. }
  482. // clang-format off
  483. /**
  484. * Test cases for perpendicularity
  485. */
  486. static const std::vector<SEG_SEG_BOOLEAN_CASE> seg_vec_perpendicular_cases = {
  487. {
  488. "coincident",
  489. { { 0, 0 }, { 10, 0 } },
  490. { { 0, 0 }, { 10, 0 } },
  491. false,
  492. },
  493. {
  494. "end-to-end",
  495. { { 0, 0 }, { 10, 0 } },
  496. { { 10, 0 }, { 20, 0 } },
  497. false,
  498. },
  499. {
  500. "In segment",
  501. { { 0, 0 }, { 10, 0 } },
  502. { { 4, 0 }, { 7, 0 } },
  503. false,
  504. },
  505. {
  506. "At side, parallel",
  507. { { 0, 0 }, { 10, 0 } },
  508. { { 4, 1 }, { 7, 1 } },
  509. false,
  510. },
  511. {
  512. "crossing 45 deg",
  513. { { 0, 0 }, { 10, 0 } },
  514. { { 0, 0 }, { 5, 5 } },
  515. false,
  516. },
  517. {
  518. "very nearly perpendicular",
  519. { { 0, 0 }, { 10, 0 } },
  520. { { 0, 0 }, { 1, 10 } },
  521. true, //allow error margin of 1 IU
  522. },
  523. {
  524. "not really perpendicular",
  525. { { 0, 0 }, { 10, 0 } },
  526. { { 0, 0 }, { 3, 10 } },
  527. false,
  528. },
  529. {
  530. "perpendicular",
  531. { { 0, 0 }, { 10, 0 } },
  532. { { 0, 0 }, { 0, 10 } },
  533. true,
  534. },
  535. {
  536. "perpendicular not intersecting",
  537. { { 0, 0 }, { 10, 0 } },
  538. { { 15, 5 }, { 15, 10 } },
  539. true,
  540. },
  541. };
  542. // clang-format on
  543. BOOST_AUTO_TEST_CASE( SegSegPerpendicular )
  544. {
  545. for( const auto& c : seg_vec_perpendicular_cases )
  546. {
  547. BOOST_TEST_CONTEXT( c.m_case_name )
  548. {
  549. BOOST_CHECK_PREDICATE( SegPerpendicularCorrect,
  550. ( c.m_seg_a )( c.m_seg_b )( c.m_exp_result ) );
  551. }
  552. }
  553. }
  554. /**
  555. * Struct to hold cases for operations with a #SEG, and a #VECTOR2I
  556. */
  557. struct SEG_VEC_CASE
  558. {
  559. std::string m_case_name;
  560. SEG m_seg;
  561. VECTOR2I m_vec;
  562. };
  563. // clang-format off
  564. /**
  565. * Test cases to create segments passing through a point
  566. */
  567. static const std::vector<SEG_VEC_CASE> segment_and_point_cases = {
  568. {
  569. "Horizontal: point on edge of seg",
  570. { { 0, 0 }, { 10, 0 } },
  571. { 0, 0 },
  572. },
  573. {
  574. "Horizontal: point in middle of seg",
  575. { { 0, 0 }, { 10, 0 } },
  576. { 5, 0 },
  577. },
  578. {
  579. "Horizontal: point outside seg",
  580. { { 0, 0 }, { 10, 0 } },
  581. { 20, 20 },
  582. },
  583. {
  584. "Vertical: point on edge of seg",
  585. { { 0, 0 }, { 0, 10 } },
  586. { 0, 0 },
  587. },
  588. {
  589. "Vertical: point in middle of seg",
  590. { { 0, 0 }, { 0, 10 } },
  591. { 0, 5 },
  592. },
  593. {
  594. "Vertical: point outside seg",
  595. { { 0, 0 }, { 0, 10 } },
  596. { 20, 20 },
  597. },
  598. };
  599. // clang-format on
  600. BOOST_AUTO_TEST_CASE( SegCreateParallel )
  601. {
  602. for( const auto& c : segment_and_point_cases )
  603. {
  604. BOOST_TEST_CONTEXT( c.m_case_name )
  605. {
  606. SEG perpendicular = c.m_seg.ParallelSeg( c.m_vec );
  607. BOOST_CHECK_PREDICATE( SegParallelCorrect, ( perpendicular )( c.m_seg )( true ) );
  608. BOOST_CHECK_PREDICATE( SegVecDistanceCorrect, ( perpendicular )( c.m_vec )( 0 ) );
  609. }
  610. }
  611. }
  612. BOOST_AUTO_TEST_CASE( SegCreatePerpendicular )
  613. {
  614. for( const auto& c : segment_and_point_cases )
  615. {
  616. BOOST_TEST_CONTEXT( c.m_case_name )
  617. {
  618. SEG perpendicular = c.m_seg.PerpendicularSeg( c.m_vec );
  619. BOOST_CHECK_PREDICATE( SegPerpendicularCorrect, ( perpendicular )( c.m_seg )( true ) );
  620. BOOST_CHECK_PREDICATE( SegVecDistanceCorrect, ( perpendicular )( c.m_vec )( 0 ) );
  621. }
  622. }
  623. }
  624. BOOST_AUTO_TEST_SUITE_END()