KiCad PCB EDA Suite
Loading...
Searching...
No Matches
test_segment.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) 2017 CERN
5 * @author Alejandro García Montoro <[email protected]>
6 * Copyright The 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, see <https://www.gnu.org/licenses/>.
20 */
21
23#include <boost/test/data/test_case.hpp>
24
25#include <geometry/seg.h>
26
27namespace
28{
29
38bool SegCollideCorrect( const SEG& aSegA, const SEG& aSegB, int aClearance, bool aExp )
39{
40 const bool AtoB = aSegA.Collide( aSegB, aClearance );
41 const bool BtoA = aSegB.Collide( aSegA, aClearance );
42
43 const bool ok = ( AtoB == aExp ) && ( BtoA == aExp );
44
45 if( AtoB != BtoA )
46 {
47 std::stringstream ss;
48 ss << "Segment collision is not the same in both directions: expected " << aExp << ", got "
49 << AtoB << " & " << BtoA;
50 BOOST_TEST_INFO( ss.str() );
51 }
52 else if( !ok )
53 {
54 std::stringstream ss;
55 ss << "Collision incorrect: expected " << aExp << ", got " << AtoB;
56 BOOST_TEST_INFO( ss.str() );
57 }
58
59 return ok;
60}
61
62
70bool SegDistanceCorrect( const SEG& aSegA, const SEG& aSegB, int aExp )
71{
72 const int AtoB = aSegA.Distance( aSegB );
73 const int BtoA = aSegB.Distance( aSegA );
74
75 bool ok = ( AtoB == aExp ) && ( BtoA == aExp );
76
77 if( AtoB != BtoA )
78 {
79 std::stringstream ss;
80 ss << "Segment distance is not the same in both directions: expected " << aExp << ", got "
81 << AtoB << " & " << BtoA;
82 BOOST_TEST_INFO( ss.str() );
83 }
84 else if( !ok )
85 {
86 std::stringstream ss;
87 ss << "Distance incorrect: expected " << aExp << ", got " << AtoB;
88 BOOST_TEST_INFO( ss.str() );
89 }
90
91 // Sanity check: the collision should be consistent with the distance
92 ok = ok && SegCollideCorrect( aSegA, aSegB, 0, aExp == 0 );
93
94 return ok;
95}
96
104bool SegVecDistanceCorrect( const SEG& aSeg, const VECTOR2I& aVec, int aExp )
105{
106 const SEG::ecoord squaredDistance = aSeg.SquaredDistance( aVec );
107 BOOST_REQUIRE( squaredDistance >= 0 );
108
109 const int dist = aSeg.Distance( aVec );
110
111 bool ok = ( dist == aExp );
112
113 if( !ok )
114 {
115 std::stringstream ss;
116 ss << "Distance incorrect: expected " << aExp << ", got " << dist;
117 BOOST_TEST_INFO( ss.str() );
118 }
119
120 return ok;
121}
122
130bool SegCollinearCorrect( const SEG& aSegA, const SEG& aSegB, bool aExp )
131{
132 const bool AtoB = aSegA.Collinear( aSegB );
133 const bool BtoA = aSegB.Collinear( aSegA );
134
135 const bool ok = ( AtoB == aExp ) && ( BtoA == aExp );
136
137 if( AtoB != BtoA )
138 {
139 std::stringstream ss;
140 ss << "Segment collinearity is not the same in both directions: expected " << aExp
141 << ", got " << AtoB << " & " << BtoA;
142 BOOST_TEST_INFO( ss.str() );
143 }
144 else if( !ok )
145 {
146 std::stringstream ss;
147 ss << "Collinearity incorrect: expected " << aExp << ", got " << AtoB;
148 BOOST_TEST_INFO( ss.str() );
149 }
150
151 return ok;
152}
153
162bool SegParallelCorrect( const SEG& aSegA, const SEG& aSegB, bool aExp )
163{
164 const bool AtoB = aSegA.ApproxParallel( aSegB );
165 const bool BtoA = aSegB.ApproxParallel( aSegA );
166
167 const bool ok = ( AtoB == aExp ) && ( BtoA == aExp );
168
169 if( AtoB != BtoA )
170 {
171 std::stringstream ss;
172 ss << "Segment parallelism is not the same in both directions: expected " << aExp
173 << ", got AtoB: " << AtoB << " BtoA:" << BtoA;
174 BOOST_TEST_INFO( ss.str() );
175 }
176 else if( !ok )
177 {
178 std::stringstream ss;
179 ss << "Parallelism incorrect: expected " << aExp << ", got " << AtoB;
180 BOOST_TEST_INFO( ss.str() );
181 }
182
183 return ok;
184}
185
194bool SegPerpendicularCorrect( const SEG& aSegA, const SEG& aSegB, bool aExp )
195{
196 const bool AtoB = aSegA.ApproxPerpendicular( aSegB );
197 const bool BtoA = aSegB.ApproxPerpendicular( aSegA );
198
199 const bool ok = ( AtoB == aExp ) && ( BtoA == aExp );
200
201 if( AtoB != BtoA )
202 {
203 std::stringstream ss;
204 ss << "Segment perpendicularity is not the same in both directions: expected " << aExp
205 << ", got AtoB: " << AtoB << " BtoA:" << BtoA;
206 BOOST_TEST_INFO( ss.str() );
207 }
208 else if( !ok )
209 {
210 std::stringstream ss;
211 ss << "Perpendicularity incorrect: expected " << aExp << ", got " << AtoB;
212 BOOST_TEST_INFO( ss.str() );
213 }
214
215 return ok;
216}
217
218} // namespace
219
220BOOST_AUTO_TEST_SUITE( Segment )
221
222
226BOOST_AUTO_TEST_CASE( EndpointCtorMod )
227{
228 const VECTOR2I pointA{ 10, 20 };
229 const VECTOR2I pointB{ 100, 200 };
230
231 // Build a segment referencing the previous points
232 SEG segment( pointA, pointB );
233
234 BOOST_CHECK_EQUAL( pointA, VECTOR2I( 10, 20 ) );
235 BOOST_CHECK_EQUAL( pointB, VECTOR2I( 100, 200 ) );
236
237 // Modify the ends of the segments
238 segment.A += VECTOR2I( 10, 10 );
239 segment.B += VECTOR2I( 100, 100 );
240
241 // Check that the ends in segment are modified
242 BOOST_CHECK_EQUAL( segment.A, VECTOR2I( 20, 30 ) );
243 BOOST_CHECK_EQUAL( segment.B, VECTOR2I( 200, 300 ) );
244}
245
252
253
254// clang-format off
255static const std::vector<SEG_SEG_DISTANCE_CASE> seg_seg_dist_cases = {
256 {
257 "Parallel, 10 apart",
258 { { 0, 0 }, { 10, 0 } },
259 { { 0, 10 }, { 10, 10 } },
260 10,
261 },
262 {
263 "Non-parallel, 10 apart",
264 { { 0, -5 }, { 10, 0 } },
265 { { 0, 10 }, { 10, 10 } },
266 10,
267 },
268 {
269 "Co-incident",
270 { { 0, 0 }, { 30, 0 } },
271 { { 10, 0 }, { 20, 0 } },
272 0,
273 },
274 {
275 "Crossing",
276 { { 0, -10 }, { 0, 10 } },
277 { { -20, 0 }, { 20, 0 } },
278 0,
279 },
280 {
281 "T-junction",
282 { { 0, -10 }, { 0, 10 } },
283 { { -20, 0 }, { 0, 0 } },
284 0,
285 },
286 {
287 "T-junction (no touch)",
288 { { 0, -10 }, { 0, 10 } },
289 { { -20, 0 }, { -2, 0 } },
290 2,
291 },
292 {
293 "Zero-length segment A",
294 { { 0, 0 }, { 0, 0 } },
295 { { 10, 0 }, { 20, 0 } },
296 10,
297 },
298 {
299 "Zero-length segment B",
300 { { 10, 0 }, { 20, 0 } },
301 { { 0, 0 }, { 0, 0 } },
302 10,
303 },
304 {
305 "Both zero-length",
306 { { 0, 0 }, { 0, 0 } },
307 { { 10, 0 }, { 10, 0 } },
308 10,
309 },
310};
311// clang-format on
312
313
314BOOST_DATA_TEST_CASE( SegSegDistance, boost::unit_test::data::make( seg_seg_dist_cases ), c )
315{
316 BOOST_CHECK_PREDICATE( SegDistanceCorrect, ( c.m_seg_a )( c.m_seg_b )( c.m_exp_dist ) );
317}
318
319
326
327
328// clang-format off
329static const std::vector<SEG_VECTOR_DISTANCE_CASE> seg_vec_dist_cases = {
330 {
331 "On endpoint",
332 { { 0, 0 }, { 10, 0 } },
333 { 0, 0 },
334 0,
335 },
336 {
337 "On segment",
338 { { 0, 0 }, { 10, 0 } },
339 { 3, 0 },
340 0,
341 },
342 {
343 "At side",
344 { { 0, 0 }, { 10, 0 } },
345 { 3, 2 },
346 2,
347 },
348 {
349 "At end (collinear)",
350 { { 0, 0 }, { 10, 0 } },
351 { 12, 0 },
352 2,
353 },
354 {
355 "At end (not collinear)",
356 { { 0, 0 }, { 1000, 0 } },
357 { 1000 + 200, 200 },
358 282, // sqrt(200^2 + 200^2) = 282.8, rounded to nearest
359 },
360 {
361 "Issue 18473 (inside hit with rounding error)",
362 { { 187360000, 42510000 }, { 105796472, 42510000 } },
363 { 106645000, 42510000 },
364 0,
365 },
366 {
367 "Straight line x distance",
368 { { 187360000, 42510000 }, { 105796472, 42510000 } },
369 { 197360000, 42510000 },
370 10000000,
371 },
372 {
373 "Straight line -x distance",
374 { { 187360000, 42510000 }, { 105796472, 42510000 } },
375 { 104796472, 42510000 },
376 1000000,
377 },
378};
379// clang-format on
380
381
382BOOST_DATA_TEST_CASE( SegVecDistance, boost::unit_test::data::make( seg_vec_dist_cases ), c )
383{
384 BOOST_CHECK_PREDICATE( SegVecDistanceCorrect, ( c.m_seg )( c.m_vec )( c.m_exp_dist ) );
385}
386
387
399
400
401// clang-format off
402static const std::vector<SEG_SEG_COLLIDE_CASE> seg_seg_coll_cases = {
403 {
404 "Parallel, 10 apart, 5 clear",
405 { { 0, 0 }, { 10, 0 } },
406 { { 0, 10 }, { 10, 10 } },
407 5,
408 false,
409 },
410 {
411 "Parallel, 10 apart, 10 clear",
412 { { 0, 0 }, { 10, 0 } },
413 { { 0, 10 }, { 10, 10 } },
414 10,
415 false,
416 },
417 {
418 "Parallel, 10 apart, 11 clear",
419 { { 0, 0 }, { 10, 0 } },
420 { { 0, 10 }, { 10, 10 } },
421 11,
422 true,
423 },
424 {
425 "T-junction, 2 apart, 2 clear",
426 { { 0, -10 }, { 0, 0 } },
427 { { -20, 0 }, { -2, 0 } },
428 2,
429 false,
430 },
431 {
432 "T-junction, 2 apart, 3 clear",
433 { { 0, -10 }, { 0, 0 } },
434 { { -20, 0 }, { -2, 0 } },
435 3,
436 true,
437 },
438 {
439 "Zero-length segment A, 10 apart",
440 { { 0, 0 }, { 0, 0 } },
441 { { 10, 0 }, { 20, 0 } },
442 0,
443 false,
444 },
445 {
446 "Zero-length segment A, 10 apart, 9 clear",
447 { { 0, 0 }, { 0, 0 } },
448 { { 10, 0 }, { 20, 0 } },
449 9,
450 false,
451 },
452 {
453 "Zero-length segment A, 10 apart, 10 clear",
454 { { 0, 0 }, { 0, 0 } },
455 { { 10, 0 }, { 20, 0 } },
456 10,
457 false,
458 },
459 {
460 "Zero-length segment A, 10 apart, 11 clear",
461 { { 0, 0 }, { 0, 0 } },
462 { { 10, 0 }, { 20, 0 } },
463 11,
464 true,
465 },
466 {
467 "Zero-length segment B, 10 apart",
468 { { 10, 0 }, { 20, 0 } },
469 { { 0, 0 }, { 0, 0 } },
470 0,
471 false,
472 },
473 {
474 "Both zero-length, same point",
475 { { 5, 5 }, { 5, 5 } },
476 { { 5, 5 }, { 5, 5 } },
477 0,
478 true,
479 },
480 {
481 "Both zero-length, 10 apart",
482 { { 0, 0 }, { 0, 0 } },
483 { { 10, 0 }, { 10, 0 } },
484 0,
485 false,
486 },
487 {
488 "Zero-length on segment",
489 { { 5, 0 }, { 5, 0 } },
490 { { 0, 0 }, { 10, 0 } },
491 0,
492 true,
493 },
494 {
495 "Zero-length near segment, x overlaps but y differs",
496 { { 5, 5 }, { 5, 5 } },
497 { { 0, 0 }, { 10, 0 } },
498 0,
499 false,
500 },
501 {
502 "Zero-length near segment, x overlaps but y differs, 4 clear",
503 { { 5, 5 }, { 5, 5 } },
504 { { 0, 0 }, { 10, 0 } },
505 4,
506 false,
507 },
508 {
509 "Zero-length near segment, x overlaps but y differs, 5 clear",
510 { { 5, 5 }, { 5, 5 } },
511 { { 0, 0 }, { 10, 0 } },
512 5,
513 false,
514 },
515 {
516 "Zero-length near segment, x overlaps but y differs, 6 clear",
517 { { 5, 5 }, { 5, 5 } },
518 { { 0, 0 }, { 10, 0 } },
519 6,
520 true,
521 },
522};
523// clang-format on
524
525
526BOOST_DATA_TEST_CASE( SegSegCollision, boost::unit_test::data::make( seg_seg_coll_cases ), c )
527{
528 BOOST_CHECK_PREDICATE( SegCollideCorrect,
529 ( c.m_seg_a )( c.m_seg_b )( c.m_clearance )( c.m_exp_coll ) );
530}
531
532
542
543// clang-format off
547static const std::vector<SEG_SEG_BOOLEAN_CASE> seg_vec_collinear_cases = {
548 {
549 "coincident",
550 { { 0, 0 }, { 10, 0 } },
551 { { 0, 0 }, { 10, 0 } },
552 true,
553 },
554 {
555 "end-to-end",
556 { { 0, 0 }, { 10, 0 } },
557 { { 10, 0 }, { 20, 0 } },
558 true,
559 },
560 {
561 "In segment",
562 { { 0, 0 }, { 10, 0 } },
563 { { 4, 0 }, { 7, 0 } },
564 true,
565 },
566 {
567 "At side, parallel",
568 { { 0, 0 }, { 10, 0 } },
569 { { 4, 1 }, { 7, 1 } },
570 false,
571 },
572 {
573 "crossing",
574 { { 0, 0 }, { 10, 0 } },
575 { { 5, -5 }, { 5, 5 } },
576 false,
577 },
578};
579// clang-format on
580
581
582BOOST_DATA_TEST_CASE( SegSegCollinear, boost::unit_test::data::make( seg_vec_collinear_cases ), c )
583{
584 BOOST_CHECK_PREDICATE( SegCollinearCorrect, ( c.m_seg_a )( c.m_seg_b )( c.m_exp_result ) );
585}
586
587
588// clang-format off
592static const std::vector<SEG_SEG_BOOLEAN_CASE> seg_vec_parallel_cases = {
593 {
594 "coincident",
595 { { 0, 0 }, { 10, 0 } },
596 { { 0, 0 }, { 10, 0 } },
597 true,
598 },
599 {
600 "end-to-end",
601 { { 0, 0 }, { 10, 0 } },
602 { { 10, 0 }, { 20, 0 } },
603 true,
604 },
605 {
606 "In segment",
607 { { 0, 0 }, { 10, 0 } },
608 { { 4, 0 }, { 7, 0 } },
609 true,
610 },
611 {
612 "At side, parallel",
613 { { 0, 0 }, { 10, 0 } },
614 { { 4, 1 }, { 7, 1 } },
615 true,
616 },
617 {
618 "crossing",
619 { { 0, 0 }, { 10, 0 } },
620 { { 5, -5 }, { 5, 5 } },
621 false,
622 },
623};
624// clang-format on
625
626
627BOOST_DATA_TEST_CASE( SegSegParallel, boost::unit_test::data::make( seg_vec_parallel_cases ), c )
628{
629 BOOST_CHECK_PREDICATE( SegParallelCorrect, ( c.m_seg_a )( c.m_seg_b )( c.m_exp_result ) );
630}
631
632
633// clang-format off
637static const std::vector<SEG_SEG_BOOLEAN_CASE> seg_vec_perpendicular_cases = {
638 {
639 "coincident",
640 { { 0, 0 }, { 10, 0 } },
641 { { 0, 0 }, { 10, 0 } },
642 false,
643 },
644 {
645 "end-to-end",
646 { { 0, 0 }, { 10, 0 } },
647 { { 10, 0 }, { 20, 0 } },
648 false,
649 },
650 {
651 "In segment",
652 { { 0, 0 }, { 10, 0 } },
653 { { 4, 0 }, { 7, 0 } },
654 false,
655 },
656 {
657 "At side, parallel",
658 { { 0, 0 }, { 10, 0 } },
659 { { 4, 1 }, { 7, 1 } },
660 false,
661 },
662 {
663 "crossing 45 deg",
664 { { 0, 0 }, { 10, 0 } },
665 { { 0, 0 }, { 5, 5 } },
666 false,
667 },
668 {
669 "very nearly perpendicular",
670 { { 0, 0 }, { 10, 0 } },
671 { { 0, 0 }, { 1, 10 } },
672 true, //allow error margin of 1 IU
673 },
674 {
675 "not really perpendicular",
676 { { 0, 0 }, { 10, 0 } },
677 { { 0, 0 }, { 3, 10 } },
678 false,
679 },
680 {
681 "perpendicular",
682 { { 0, 0 }, { 10, 0 } },
683 { { 0, 0 }, { 0, 10 } },
684 true,
685 },
686 {
687 "perpendicular not intersecting",
688 { { 0, 0 }, { 10, 0 } },
689 { { 15, 5 }, { 15, 10 } },
690 true,
691 },
692};
693// clang-format on
694
695
696BOOST_DATA_TEST_CASE( SegSegPerpendicular,
697 boost::unit_test::data::make( seg_vec_perpendicular_cases ), c )
698{
699 BOOST_CHECK_PREDICATE( SegPerpendicularCorrect, ( c.m_seg_a )( c.m_seg_b )( c.m_exp_result ) );
700}
701
702
711
712
713// clang-format off
717static const std::vector<SEG_VEC_CASE> segment_and_point_cases = {
718 {
719 "Horizontal: point on edge of seg",
720 { { 0, 0 }, { 10, 0 } },
721 { 0, 0 },
722 },
723 {
724 "Horizontal: point in middle of seg",
725 { { 0, 0 }, { 10, 0 } },
726 { 5, 0 },
727 },
728 {
729 "Horizontal: point outside seg",
730 { { 0, 0 }, { 10, 0 } },
731 { 20, 20 },
732 },
733 {
734 "Vertical: point on edge of seg",
735 { { 0, 0 }, { 0, 10 } },
736 { 0, 0 },
737 },
738 {
739 "Vertical: point in middle of seg",
740 { { 0, 0 }, { 0, 10 } },
741 { 0, 5 },
742 },
743 {
744 "Vertical: point outside seg",
745 { { 0, 0 }, { 0, 10 } },
746 { 20, 20 },
747 },
748};
749// clang-format on
750
751
752BOOST_DATA_TEST_CASE( SegCreateParallel, boost::unit_test::data::make( segment_and_point_cases ),
753 c )
754{
755 const SEG perpendicular = c.m_seg.ParallelSeg( c.m_vec );
756
757 BOOST_CHECK_PREDICATE( SegParallelCorrect, (perpendicular) ( c.m_seg )( true ) );
758 BOOST_CHECK_PREDICATE( SegVecDistanceCorrect, (perpendicular) ( c.m_vec )( 0 ) );
759}
760
761BOOST_DATA_TEST_CASE( SegCreatePerpendicular,
762 boost::unit_test::data::make( segment_and_point_cases ), c )
763{
764 const SEG perpendicular = c.m_seg.PerpendicularSeg( c.m_vec );
765
766 BOOST_CHECK_PREDICATE( SegPerpendicularCorrect, (perpendicular) ( c.m_seg )( true ) );
767 BOOST_CHECK_PREDICATE( SegVecDistanceCorrect, (perpendicular) ( c.m_vec )( 0 ) );
768}
769
770BOOST_AUTO_TEST_CASE( LineDistance )
771{
772 SEG seg( { 0, 0 }, { 10, 0 } );
773
774 BOOST_TEST( seg.LineDistance( { 5, 0 } ) == 0 );
775 BOOST_TEST( seg.LineDistance( { 5, 8 } ) == 8 );
776}
777
778BOOST_AUTO_TEST_CASE( LineDistanceSided )
779{
780 SEG seg( { 0, 0 }, { 10, 0 } );
781
782 BOOST_TEST( seg.LineDistance( { 5, 8 }, true ) == 8 );
783 BOOST_TEST( seg.LineDistance( { 5, -8 }, true ) == -8 );
784}
785
798
799// clang-format off
800static const std::vector<SEG_SEG_INTERSECT_CASE> seg_intersect_cases = {
801 // Basic crossing cases
802 {
803 "Crossing at origin",
804 { { -10, 0 }, { 10, 0 } },
805 { { 0, -10 }, { 0, 10 } },
806 false, false, true,
807 { 0, 0 }
808 },
809 {
810 "Crossing at (5,5)",
811 { { 0, 5 }, { 10, 5 } },
812 { { 5, 0 }, { 5, 10 } },
813 false, false, true,
814 { 5, 5 }
815 },
816 {
817 "T-junction intersection",
818 { { 0, 0 }, { 10, 0 } },
819 { { 5, -5 }, { 5, 0 } },
820 false, false, true,
821 { 5, 0 }
822 },
823
824 // Non-intersecting cases
825 {
826 "Parallel segments",
827 { { 0, 0 }, { 10, 0 } },
828 { { 0, 5 }, { 10, 5 } },
829 false, false, false,
830 { 0, 0 }
831 },
832 {
833 "Separated segments",
834 { { 0, 0 }, { 5, 0 } },
835 { { 10, 0 }, { 15, 0 } },
836 false, false, false,
837 { 0, 0 }
838 },
839 {
840 "Lines would intersect, but segments don't",
841 { { 0, 0 }, { 2, 0 } },
842 { { 5, -5 }, { 5, 5 } },
843 false, false, false,
844 { 0, 0 }
845 },
846
847 // Endpoint intersection cases
848 {
849 "Endpoint touching - should intersect",
850 { { 0, 0 }, { 10, 0 } },
851 { { 10, 0 }, { 20, 0 } },
852 false, false, true,
853 { 10, 0 }
854 },
855 {
856 "Endpoint touching - ignore endpoints",
857 { { 0, 0 }, { 10, 0 } },
858 { { 10, 0 }, { 20, 0 } },
859 true, false, false,
860 { 0, 0 }
861 },
862 {
863 "Endpoint touching at angle",
864 { { 0, 0 }, { 10, 0 } },
865 { { 10, 0 }, { 15, 5 } },
866 false, false, true,
867 { 10, 0 }
868 },
869
870 // Collinear cases
871 {
872 "Collinear overlapping segments",
873 { { 0, 0 }, { 10, 0 } },
874 { { 5, 0 }, { 15, 0 } },
875 false, false, true,
876 { 7, 0 } // Midpoint of overlap [5,10]
877 },
878 {
879 "Collinear non-overlapping segments",
880 { { 0, 0 }, { 5, 0 } },
881 { { 10, 0 }, { 15, 0 } },
882 false, false, false,
883 { 0, 0 }
884 },
885 {
886 "Collinear touching at endpoint",
887 { { 0, 0 }, { 10, 0 } },
888 { { 10, 0 }, { 20, 0 } },
889 false, false, true,
890 { 10, 0 }
891 },
892 {
893 "Collinear contained segment",
894 { { 0, 0 }, { 20, 0 } },
895 { { 5, 0 }, { 15, 0 } },
896 false, false, true,
897 { 10, 0 } // Midpoint of contained segment
898 },
899 {
900 "Collinear vertical overlapping",
901 { { 5, 0 }, { 5, 10 } },
902 { { 5, 5 }, { 5, 15 } },
903 false, false, true,
904 { 5, 7 } // Midpoint of overlap [5,10]
905 },
906
907 // Line mode cases (infinite lines)
908 {
909 "Lines intersect, segments don't",
910 { { 0, 0 }, { 2, 0 } },
911 { { 5, -5 }, { 5, 5 } },
912 false, true, true,
913 { 5, 0 }
914 },
915 {
916 "Parallel lines (infinite)",
917 { { 0, 0 }, { 10, 0 } },
918 { { 0, 5 }, { 10, 5 } },
919 false, true, false,
920 { 0, 0 }
921 },
922 {
923 "Collinear lines (infinite)",
924 { { 0, 0 }, { 10, 0 } },
925 { { 20, 0 }, { 30, 0 } },
926 false, true, true,
927 { 10, 0 } // Midpoint between segment starts
928 },
929
930 // Edge cases
931 {
932 "Zero-length segment intersection",
933 { { 5, 5 }, { 5, 5 } },
934 { { 0, 5 }, { 10, 5 } },
935 false, false, true,
936 { 5, 5 }
937 },
938 {
939 "Both zero-length, same point",
940 { { 5, 5 }, { 5, 5 } },
941 { { 5, 5 }, { 5, 5 } },
942 false, false, true,
943 { 5, 5 }
944 },
945 {
946 "Both zero-length, different points",
947 { { 5, 5 }, { 5, 5 } },
948 { { 10, 10 }, { 10, 10 } },
949 false, false, false,
950 { 0, 0 }
951 },
952
953 // Diagonal intersection cases
954 {
955 "45-degree crossing",
956 { { 0, 0 }, { 10, 10 } },
957 { { 0, 10 }, { 10, 0 } },
958 false, false, true,
959 { 5, 5 }
960 },
961 {
962 "Arbitrary angle crossing",
963 { { 0, 0 }, { 6, 8 } },
964 { { 0, 8 }, { 6, 0 } },
965 false, false, true,
966 { 3, 4 }
967 },
968
969 // Bounding box optimization test cases
970 {
971 "Far apart horizontal segments",
972 { { 0, 0 }, { 10, 0 } },
973 { { 100, 0 }, { 110, 0 } },
974 false, false, false,
975 { 0, 0 }
976 },
977 {
978 "Far apart vertical segments",
979 { { 0, 0 }, { 0, 10 } },
980 { { 0, 100 }, { 0, 110 } },
981 false, false, false,
982 { 0, 0 }
983 },
984 {
985 "Far apart diagonal segments",
986 { { 0, 0 }, { 10, 10 } },
987 { { 100, 100 }, { 110, 110 } },
988 false, false, false,
989 { 0, 0 }
990 },
991};
992// clang-format on
993
994
1001{
1002 const auto resultA = aCase.m_seg_a.Intersect( aCase.m_seg_b, aCase.m_ignore_endpoints, aCase.m_lines );
1003 const auto resultB = aCase.m_seg_b.Intersect( aCase.m_seg_a, aCase.m_ignore_endpoints, aCase.m_lines );
1004
1005 const bool intersectsA = resultA.has_value();
1006 const bool intersectsB = resultB.has_value();
1007
1008 bool ok = ( intersectsA == aCase.m_exp_intersect ) && ( intersectsB == aCase.m_exp_intersect );
1009
1010 if( intersectsA != intersectsB )
1011 {
1012 std::stringstream ss;
1013 ss << "Segment intersection is not the same in both directions: expected " << aCase.m_exp_intersect
1014 << ", got " << intersectsA << " & " << intersectsB;
1015 BOOST_TEST_INFO( ss.str() );
1016 ok = false;
1017 }
1018 else if( !ok )
1019 {
1020 std::stringstream ss;
1021 ss << "Intersection incorrect: expected " << aCase.m_exp_intersect << ", got " << intersectsA;
1022 BOOST_TEST_INFO( ss.str() );
1023 }
1024
1025 // Check intersection point if intersection was expected
1026 if( ok && aCase.m_exp_intersect && aCase.m_exp_point != VECTOR2I() )
1027 {
1028 // Allow some tolerance for intersection point calculation
1029 const int tolerance = 1;
1030
1031 if( !resultA || !resultB )
1032 {
1033 std::stringstream ss;
1034 ss << "Expected intersection but got nullopt";
1035 BOOST_TEST_INFO( ss.str() );
1036 ok = false;
1037 }
1038 else
1039 {
1040 const VECTOR2I pointA = *resultA;
1041 const VECTOR2I pointB = *resultB;
1042
1043 bool pointOk = ( std::abs( pointA.x - aCase.m_exp_point.x ) <= tolerance &&
1044 std::abs( pointA.y - aCase.m_exp_point.y ) <= tolerance &&
1045 std::abs( pointB.x - aCase.m_exp_point.x ) <= tolerance &&
1046 std::abs( pointB.y - aCase.m_exp_point.y ) <= tolerance );
1047
1048 if( !pointOk )
1049 {
1050 std::stringstream ss;
1051 ss << "Intersection point incorrect: expected " << aCase.m_exp_point.Format()
1052 << ", got " << pointA.Format() << " & " << pointB.Format();
1053 BOOST_TEST_INFO( ss.str() );
1054 ok = false;
1055 }
1056 }
1057 }
1058
1059 return ok;
1060}
1061
1062BOOST_DATA_TEST_CASE( SegSegIntersection, boost::unit_test::data::make( seg_intersect_cases ), c )
1063{
1065}
1066
1067
1068// Additional focused test cases for specific scenarios
1069BOOST_AUTO_TEST_CASE( IntersectLargeCoordinates )
1070{
1071 // Test with large coordinates to verify overflow protection
1072 SEG segA( { 1000000000, 0 }, { -1000000000, 0 } );
1073 SEG segB( { 0, 1000000000 }, { 0, -1000000000 } );
1074
1075 auto intersection = segA.Intersect( segB, false, false );
1076
1077 BOOST_CHECK( intersection.has_value() );
1078 BOOST_CHECK_EQUAL( intersection->x, 0 );
1079 BOOST_CHECK_EQUAL( intersection->y, 0 );
1080}
1081
1082BOOST_AUTO_TEST_CASE( IntersectOverflowDetection )
1083{
1084 // Test intersection that would overflow coordinate range
1085 constexpr int max_coord = std::numeric_limits<int>::max();
1086
1087 SEG segA( { 0, 0 }, { max_coord, max_coord } );
1088 SEG segB( { max_coord, 0 }, { 0, max_coord } );
1089
1090 // This should either work or return nullopt due to overflow protection
1091 auto intersection = segA.Intersect( segB, false, false );
1092
1093 // The test passes if it doesn't crash - the exact result depends on overflow handling
1094 BOOST_TEST_MESSAGE( "Overflow test completed without crash. Has intersection: " << intersection.has_value() );
1095 if( intersection.has_value() )
1096 {
1097 BOOST_TEST_MESSAGE( "Intersection point: " << intersection->Format() );
1098 }
1099}
1100
1101BOOST_AUTO_TEST_CASE( IntersectPrecisionEdgeCases )
1102{
1103 // Test cases that might have precision issues
1104 SEG segA( { 0, 0 }, { 1000000, 1 } );
1105 SEG segB( { 500000, -1 }, { 500000, 2 } );
1106
1107 auto intersection = segA.Intersect( segB, false, false );
1108
1109 BOOST_CHECK( intersection.has_value() );
1110 // The intersection should be very close to (500000, 0.5), rounded to (500000, 1) or (500000, 0)
1111 BOOST_CHECK_EQUAL( intersection->x, 500000 );
1112 BOOST_CHECK( intersection->y >= 0 && intersection->y <= 1 );
1113}
1114
1115BOOST_AUTO_TEST_CASE( IntersectIgnoreEndpointsEdgeCases )
1116{
1117 // Test edge cases with ignore endpoints
1118 SEG segA( { 0, 0 }, { 10, 0 } );
1119 SEG segB( { 5, -5 }, { 5, 5 } );
1120
1121 // Normal intersection should work
1122 auto intersection1 = segA.Intersect( segB, false, false );
1123 BOOST_CHECK( intersection1.has_value() );
1124 BOOST_CHECK_EQUAL( *intersection1, VECTOR2I( 5, 0 ) );
1125
1126 // Should still work when ignoring endpoints (this is a middle intersection)
1127 auto intersection2 = segA.Intersect( segB, true, false );
1128 BOOST_CHECK( intersection2.has_value() );
1129 BOOST_CHECK_EQUAL( *intersection2, VECTOR2I( 5, 0 ) );
1130
1131 // Test actual endpoint intersection
1132 SEG segC( { 10, 0 }, { 20, 0 } );
1133 auto intersection3 = segA.Intersect( segC, false, false );
1134 BOOST_CHECK( intersection3.has_value() );
1135 BOOST_CHECK_EQUAL( *intersection3, VECTOR2I( 10, 0 ) );
1136
1137 // Should not intersect when ignoring endpoints
1138 auto intersection4 = segA.Intersect( segC, true, false );
1139 BOOST_CHECK( !intersection4.has_value() );
1140}
1141
1142BOOST_AUTO_TEST_CASE( IntersectCollinearRegressionTests )
1143{
1144 // Regression tests for collinear segment handling
1145
1146 // Test case: horizontal segments with partial overlap
1147 SEG seg1( { 0, 5 }, { 10, 5 } );
1148 SEG seg2( { 5, 5 }, { 15, 5 } );
1149
1150 auto intersection = seg1.Intersect( seg2, false, false );
1151
1152 BOOST_CHECK( intersection.has_value() );
1153 BOOST_CHECK_EQUAL( intersection->y, 5 );
1154 BOOST_CHECK( intersection->x >= 5 && intersection->x <= 10 ); // Should be in overlap region
1155
1156 // Test case: vertical segments with complete overlap (one contained in other)
1157 SEG seg3( { 3, 0 }, { 3, 20 } );
1158 SEG seg4( { 3, 5 }, { 3, 15 } );
1159
1160 auto intersection2 = seg3.Intersect( seg4, false, false );
1161
1162 BOOST_CHECK( intersection2.has_value() );
1163 BOOST_CHECK_EQUAL( intersection2->x, 3 );
1164 BOOST_CHECK( intersection2->y >= 5 && intersection2->y <= 15 ); // Should be in contained segment
1165
1166 // Test case: diagonal collinear segments
1167 SEG seg5( { 0, 0 }, { 10, 10 } );
1168 SEG seg6( { 5, 5 }, { 15, 15 } );
1169
1170 auto intersection3 = seg5.Intersect( seg6, false, false );
1171
1172 BOOST_CHECK( intersection3.has_value() );
1173 BOOST_CHECK( intersection3->x >= 5 && intersection3->x <= 10 );
1174 BOOST_CHECK( intersection3->y >= 5 && intersection3->y <= 10 );
1175 BOOST_CHECK_EQUAL( intersection3->x, intersection3->y ); // Should maintain diagonal relationship
1176
1177 // Test case: collinear segments that touch only at endpoints
1178 SEG seg7( { 0, 0 }, { 5, 0 } );
1179 SEG seg8( { 5, 0 }, { 10, 0 } );
1180
1181 auto intersection4 = seg7.Intersect( seg8, false, false );
1182 BOOST_CHECK( intersection4.has_value() );
1183 BOOST_CHECK_EQUAL( *intersection4, VECTOR2I( 5, 0 ) );
1184
1185 // Same test but ignoring endpoints
1186 auto intersection5 = seg7.Intersect( seg8, true, false );
1187 BOOST_CHECK( !intersection5.has_value() );
1188
1189 // Test case: collinear segments that don't overlap
1190 SEG seg9( { 0, 0 }, { 5, 0 } );
1191 SEG seg10( { 10, 0 }, { 15, 0 } );
1192
1193 auto intersection6 = seg9.Intersect( seg10, false, false );
1194 BOOST_CHECK( !intersection6.has_value() );
1195}
1196
1197BOOST_AUTO_TEST_CASE( IntersectBoundingBoxOptimization )
1198{
1199 // Test that bounding box optimization works correctly
1200
1201 // Segments that are clearly separated - should be rejected quickly
1202 SEG seg1( { 0, 0 }, { 10, 10 } );
1203 SEG seg2( { 100, 100 }, { 110, 110 } );
1204
1205 auto intersection = seg1.Intersect( seg2, false, false );
1206 BOOST_CHECK( !intersection.has_value() );
1207
1208 // Segments with overlapping bounding boxes but no intersection
1209 SEG seg3( { 0, 0 }, { 10, 0 } );
1210 SEG seg4( { 5, 5 }, { 15, 5 } );
1211
1212 auto intersection2 = seg3.Intersect( seg4, false, false );
1213 BOOST_CHECK( !intersection2.has_value() );
1214
1215 // Segments with touching bounding boxes and actual intersection
1216 SEG seg5( { 0, 0 }, { 10, 10 } );
1217 SEG seg6( { 10, 0 }, { 0, 10 } );
1218
1219 auto intersection3 = seg5.Intersect( seg6, false, false );
1220 BOOST_CHECK( intersection3.has_value() );
1221 BOOST_CHECK_EQUAL( *intersection3, VECTOR2I( 5, 5 ) );
1222}
1223
1224BOOST_AUTO_TEST_CASE( IntersectLineVsSegmentMode )
1225{
1226 // Test the difference between line mode and segment mode
1227
1228 SEG seg1( { 0, 0 }, { 5, 0 } );
1229 SEG seg2( { 10, -5 }, { 10, 5 } );
1230
1231 // In segment mode, these don't intersect
1232 auto segmentIntersect = seg1.Intersect( seg2, false, false );
1233 BOOST_CHECK( !segmentIntersect.has_value() );
1234
1235 // In line mode, they should intersect
1236 auto lineIntersect = seg1.Intersect( seg2, false, true );
1237 BOOST_CHECK( lineIntersect.has_value() );
1238 BOOST_CHECK_EQUAL( *lineIntersect, VECTOR2I( 10, 0 ) );
1239
1240 // Test collinear case in line mode
1241 SEG seg3( { 0, 0 }, { 10, 0 } );
1242 SEG seg4( { 20, 0 }, { 30, 0 } );
1243
1244 // Segments don't intersect
1245 auto segmentIntersect2 = seg3.Intersect( seg4, false, false );
1246 BOOST_CHECK( !segmentIntersect2.has_value() );
1247
1248 // Lines (infinite) do intersect (collinear)
1249 auto lineIntersect2 = seg3.Intersect( seg4, false, true );
1250 BOOST_CHECK( lineIntersect2.has_value() );
1251}
1252
1253BOOST_AUTO_TEST_CASE( IntersectNumericalStability )
1254{
1255 // Test cases designed to stress numerical precision
1256
1257 // Very small segments
1258 SEG seg1( { 0, 0 }, { 1, 1 } );
1259 SEG seg2( { 0, 1 }, { 1, 0 } );
1260
1261 auto intersection = seg1.Intersect( seg2, false, false );
1262
1263 BOOST_CHECK( intersection.has_value() );
1264 // Intersection should be very close to (0.5, 0.5), rounded to (0,0), (0,1), (1,0), or (1,1)
1265 BOOST_CHECK( intersection->x >= 0 && intersection->x <= 1 );
1266 BOOST_CHECK( intersection->y >= 0 && intersection->y <= 1 );
1267
1268 // Nearly parallel segments
1269 SEG seg3( { 0, 0 }, { 1000, 1 } );
1270 SEG seg4( { 0, 1 }, { 1000, 2 } );
1271
1272 auto intersection2 = seg3.Intersect( seg4, false, false );
1273 BOOST_CHECK( !intersection2.has_value() ); // Should be detected as parallel/non-intersecting
1274
1275 // Segments that intersect at a very acute angle
1276 SEG seg5( { 0, 0 }, { 1000000, 1 } );
1277 SEG seg6( { 500000, -1 }, { 500000, 2 } );
1278
1279 auto intersection3 = seg5.Intersect( seg6, false, false );
1280 BOOST_CHECK( intersection3.has_value() );
1281 BOOST_CHECK_EQUAL( intersection3->x, 500000 );
1282}
1283
1284BOOST_AUTO_TEST_CASE( IntersectZeroLengthSegments )
1285{
1286 // Comprehensive tests for zero-length segments (points)
1287
1288 VECTOR2I point1( 5, 5 );
1289 VECTOR2I point2( 10, 10 );
1290
1291 SEG pointSeg1( point1, point1 ); // Zero-length segment (point)
1292 SEG pointSeg2( point2, point2 ); // Another zero-length segment
1293 SEG normalSeg( { 0, 5 }, { 10, 5 } ); // Normal segment
1294
1295 // Point intersecting with normal segment
1296 auto intersection1 = pointSeg1.Intersect( normalSeg, false, false );
1297 BOOST_CHECK( intersection1.has_value() );
1298 BOOST_CHECK_EQUAL( *intersection1, point1 );
1299
1300 // Point not intersecting with normal segment
1301 auto intersection2 = pointSeg2.Intersect( normalSeg, false, false );
1302 BOOST_CHECK( !intersection2.has_value() );
1303
1304 // Two points at same location
1305 SEG pointSeg3( point1, point1 );
1306 auto intersection3 = pointSeg1.Intersect( pointSeg3, false, false );
1307 BOOST_CHECK( intersection3.has_value() );
1308 BOOST_CHECK_EQUAL( *intersection3, point1 );
1309
1310 // Two points at different locations
1311 auto intersection4 = pointSeg1.Intersect( pointSeg2, false, false );
1312 BOOST_CHECK( !intersection4.has_value() );
1313
1314 // Point on line (infinite mode)
1315 SEG lineSeg( { 0, 0 }, { 1, 1 } ); // Diagonal line segment
1316 SEG pointOnLine( { 100, 100 }, { 100, 100 } ); // Point on extended line
1317
1318 auto intersection5 = pointOnLine.Intersect( lineSeg, false, false );
1319 BOOST_CHECK( !intersection5.has_value() ); // Point not on segment
1320
1321 auto intersection6 = pointOnLine.Intersect( lineSeg, false, true );
1322 BOOST_CHECK( intersection6.has_value() ); // Point on infinite line
1323 BOOST_CHECK_EQUAL( *intersection6, VECTOR2I( 100, 100 ) );
1324}
1325
1326
1338
1348bool SegLineIntersectCorrect( const SEG& aSeg, double aSlope, double aOffset,
1349 bool aExpIntersect, const VECTOR2I& aExpPoint = VECTOR2I() )
1350{
1351 VECTOR2I intersection;
1352 const bool intersects = aSeg.IntersectsLine( aSlope, aOffset, intersection );
1353
1354 bool ok = ( intersects == aExpIntersect );
1355
1356 if( !ok )
1357 {
1358 std::stringstream ss;
1359 ss << "Line intersection incorrect: expected " << aExpIntersect << ", got " << intersects;
1360 BOOST_TEST_INFO( ss.str() );
1361 }
1362
1363 // Check intersection point if intersection was expected
1364 if( ok && aExpIntersect && aExpPoint != VECTOR2I() )
1365 {
1366 // Allow some tolerance for intersection point calculation
1367 const int tolerance = 1;
1368
1369 bool pointOk = ( std::abs( intersection.x - aExpPoint.x ) <= tolerance &&
1370 std::abs( intersection.y - aExpPoint.y ) <= tolerance );
1371
1372 if( !pointOk )
1373 {
1374 std::stringstream ss;
1375 ss << "Intersection point incorrect: expected " << aExpPoint.Format()
1376 << ", got " << intersection.Format();
1377 BOOST_TEST_INFO( ss.str() );
1378 ok = false;
1379 }
1380 }
1381
1382 return ok;
1383}
1384
1385// clang-format off
1386static const std::vector<SEG_LINE_INTERSECT_CASE> seg_line_intersect_cases = {
1387 // Basic intersection cases
1388 {
1389 "Horizontal segment, diagonal line",
1390 { { 0, 5 }, { 10, 5 } },
1391 1.0, 0.0, // y = x
1392 true,
1393 { 5, 5 }
1394 },
1395 {
1396 "Vertical segment, horizontal line",
1397 { { 5, 0 }, { 5, 10 } },
1398 0.0, 3.0, // y = 3
1399 true,
1400 { 5, 3 }
1401 },
1402 {
1403 "Diagonal segment, horizontal line crossing",
1404 { { 0, 0 }, { 10, 10 } },
1405 0.0, 5.0, // y = 5
1406 true,
1407 { 5, 5 }
1408 },
1409 {
1410 "Diagonal segment, vertical line (steep slope)",
1411 { { 0, 0 }, { 10, 10 } },
1412 1000.0, -5000.0, // Very steep line: y = 1000x - 5000, crosses at x=5
1413 true,
1414 { 5, 5 }
1415 },
1416
1417 // Non-intersecting cases
1418 {
1419 "Horizontal segment, parallel horizontal line",
1420 { { 0, 5 }, { 10, 5 } },
1421 0.0, 10.0, // y = 10 (parallel to y = 5)
1422 false,
1423 { 0, 0 }
1424 },
1425 {
1426 "Diagonal segment, parallel line",
1427 { { 0, 0 }, { 10, 10 } },
1428 1.0, 5.0, // y = x + 5 (parallel to y = x)
1429 false,
1430 { 0, 0 }
1431 },
1432 {
1433 "Segment above line",
1434 { { 0, 10 }, { 10, 10 } },
1435 0.0, 5.0, // y = 5
1436 false,
1437 { 0, 0 }
1438 },
1439 {
1440 "Segment to left of steep line",
1441 { { 0, 0 }, { 2, 2 } },
1442 1.0, 10.0, // y = x + 10
1443 false,
1444 { 0, 0 }
1445 },
1446
1447 // Collinear cases (segment lies on line)
1448 {
1449 "Horizontal segment on horizontal line",
1450 { { 0, 5 }, { 10, 5 } },
1451 0.0, 5.0, // y = 5
1452 true,
1453 { 5, 5 } // Midpoint
1454 },
1455 {
1456 "Diagonal segment on diagonal line",
1457 { { 0, 0 }, { 10, 10 } },
1458 1.0, 0.0, // y = x
1459 true,
1460 { 5, 5 } // Midpoint
1461 },
1462 {
1463 "Vertical segment, any line slope (collinear impossible)",
1464 { { 5, 0 }, { 5, 10 } },
1465 2.0, -5.0, // y = 2x - 5, passes through (5, 5)
1466 true,
1467 { 5, 5 }
1468 },
1469
1470 // Edge cases
1471 {
1472 "Zero-length segment (point) on line",
1473 { { 3, 7 }, { 3, 7 } },
1474 2.0, 1.0, // y = 2x + 1, point (3,7) should be on this line
1475 true,
1476 { 3, 7 }
1477 },
1478 {
1479 "Zero-length segment (point) not on line",
1480 { { 3, 5 }, { 3, 5 } },
1481 2.0, 1.0, // y = 2x + 1, point (3,5) not on line (should be y=7)
1482 false,
1483 { 0, 0 }
1484 },
1485 {
1486 "Line with zero slope (horizontal)",
1487 { { 0, 0 }, { 10, 5 } },
1488 0.0, 2.5, // y = 2.5
1489 true,
1490 { 5, 2 } // Intersection at x=5, y=2.5 rounded to y=2 or 3
1491 },
1492 {
1493 "Very steep positive slope",
1494 { { 0, 0 }, { 10, 1 } },
1495 100.0, -250.0, // y = 100x - 250, intersects at x=2.5
1496 true,
1497 { 2, 0 } // Approximately (2.5, 0)
1498 },
1499 {
1500 "Very steep negative slope",
1501 { { 0, 0 }, { 10, 10 } },
1502 -100.0, 505.0, // y = -100x + 505, intersects at x=5.05, y≈0
1503 true,
1504 { 5, 5 } // Approximately (5.05, 0) but segment has y=5 at x=5
1505 },
1506 {
1507 "Fractional slope",
1508 { { 0, 0 }, { 12, 8 } },
1509 0.5, 1.0, // y = 0.5x + 1
1510 true,
1511 { 6, 4 } // Intersection where segment y = 2x/3 meets line y = 0.5x + 1
1512 },
1513
1514 // Endpoint intersections
1515 {
1516 "Line passes through segment start point",
1517 { { 2, 3 }, { 80, 90 } },
1518 1.0, 1.0, // y = x + 1, passes through (2,3)
1519 true,
1520 { 2, 3 }
1521 },
1522 {
1523 "Line passes through segment end point",
1524 { { 20, 30 }, { 8, 9 } },
1525 1.0, 1.0, // y = x + 1, passes through (8,9)
1526 true,
1527 { 8, 9 }
1528 },
1529 {
1530 "Line intersects near endpoint",
1531 { { 0, 0 }, { 10, 0 } },
1532 0.0, 0.0, // y = 0, same as segment
1533 true,
1534 { 5, 0 } // Collinear, returns midpoint
1535 },
1536
1537 // Precision edge cases
1538 {
1539 "Nearly parallel lines",
1540 { { 0, 0 }, { 1000, 1 } },
1541 0.0011, -0.05, // Very slightly different slope
1542 true,
1543 { 500, 1 } // At 500, y will round up to 1 in both cases
1544 },
1545 {
1546 "Line intersection outside segment bounds",
1547 { { 5, 5 }, { 10, 10 } },
1548 1.0, -10.0, // y = x - 10, would intersect extended line at (15, 5)
1549 false,
1550 { 0, 0 }
1551 },
1552};
1553// clang-format on
1554
1555BOOST_DATA_TEST_CASE( SegLineIntersection, boost::unit_test::data::make( seg_line_intersect_cases ), c )
1556{
1557 BOOST_CHECK_PREDICATE( SegLineIntersectCorrect, ( c.m_seg )( c.m_slope )( c.m_offset )( c.m_exp_intersect )( c.m_exp_point ) );
1558}
1559
1560// Additional focused test cases for specific scenarios
1561BOOST_AUTO_TEST_CASE( IntersectLineVerticalSegments )
1562{
1563 // Test vertical segments with various line slopes
1564 SEG verticalSeg( { 5, 0 }, { 5, 10 } );
1565 VECTOR2I intersection;
1566
1567 // Horizontal line intersecting vertical segment
1568 bool intersects1 = verticalSeg.IntersectsLine( 0.0, 7.0, intersection );
1569 BOOST_CHECK( intersects1 );
1570 BOOST_CHECK_EQUAL( intersection, VECTOR2I( 5, 7 ) );
1571
1572 // Diagonal line intersecting vertical segment
1573 bool intersects2 = verticalSeg.IntersectsLine( 2.0, -5.0, intersection ); // y = 2x - 5
1574 BOOST_CHECK( intersects2 );
1575 BOOST_CHECK_EQUAL( intersection, VECTOR2I( 5, 5 ) ); // At x=5: y = 2*5 - 5 = 5
1576
1577 // Line that misses vertical segment
1578 bool intersects3 = verticalSeg.IntersectsLine( 1.0, 20.0, intersection ); // y = x + 20
1579 BOOST_CHECK( !intersects3 );
1580}
1581
1582BOOST_AUTO_TEST_CASE( IntersectLineVerticalSegmentsCorrection )
1583{
1584 // Corrected test for vertical segments
1585 SEG verticalSeg( { 5, 0 }, { 5, 10 } );
1586 VECTOR2I intersection;
1587
1588 // Line that misses vertical segment (intersection outside y-range)
1589 bool intersects1 = verticalSeg.IntersectsLine( 1.0, 20.0, intersection ); // y = x + 20
1590 BOOST_CHECK( !intersects1 ); // At x=5: y = 25, which is outside [0,10]
1591
1592 // Line that intersects within segment bounds
1593 bool intersects2 = verticalSeg.IntersectsLine( 0.5, 2.0, intersection ); // y = 0.5x + 2
1594 BOOST_CHECK( intersects2 );
1595 BOOST_CHECK_EQUAL( intersection, VECTOR2I( 5, 5 ) ); // At x=5: y = 0.5*5 + 2 = 4.5 ≈ 5 (round up)
1596}
1597
1598BOOST_AUTO_TEST_CASE( IntersectLineParallelDetection )
1599{
1600 // Test parallel line detection using cross products
1601
1602 // Horizontal segment with horizontal line
1603 SEG horizontalSeg( { 0, 5 }, { 10, 5 } );
1604 VECTOR2I intersection;
1605
1606 // Parallel but not collinear
1607 bool intersects1 = horizontalSeg.IntersectsLine( 0.0, 8.0, intersection ); // y = 8
1608 BOOST_CHECK( !intersects1 );
1609
1610 // Collinear (segment lies on line)
1611 bool intersects2 = horizontalSeg.IntersectsLine( 0.0, 5.0, intersection ); // y = 5
1612 BOOST_CHECK( intersects2 );
1613 BOOST_CHECK_EQUAL( intersection, VECTOR2I( 5, 5 ) ); // Midpoint
1614
1615 // Diagonal segment with parallel line
1616 SEG diagonalSeg( { 0, 0 }, { 10, 10 } );
1617
1618 // Parallel but offset
1619 bool intersects3 = diagonalSeg.IntersectsLine( 1.0, 3.0, intersection ); // y = x + 3
1620 BOOST_CHECK( !intersects3 );
1621
1622 // Collinear
1623 bool intersects4 = diagonalSeg.IntersectsLine( 1.0, 0.0, intersection ); // y = x
1624 BOOST_CHECK( intersects4 );
1625 BOOST_CHECK_EQUAL( intersection, VECTOR2I( 5, 5 ) ); // Midpoint
1626}
1627
1628BOOST_AUTO_TEST_CASE( IntersectLinePrecisionEdgeCases )
1629{
1630 // Test precision-sensitive cases
1631
1632 // Very shallow segment with steep line
1633 SEG shallowSeg( { 0, 100 }, { 1000000, 101 } ); // Almost horizontal
1634 VECTOR2I intersection;
1635
1636 bool intersects = shallowSeg.IntersectsLine( 1000.0, -499900.0, intersection );
1637 // Line: y = 1000x - 499900
1638 // This should intersect around x = 500, y ≈ 100.001
1639
1640 if( intersects )
1641 {
1642 BOOST_CHECK( intersection.x >= 0 && intersection.x <= 1000000 );
1643 BOOST_CHECK( intersection.y >= 100 && intersection.y <= 101 );
1644 }
1645
1646 // Test with very large coordinates
1647 SEG largeSeg( { 1000000, 1000000 }, { 2000000, 2000000 } );
1648 bool intersects2 = largeSeg.IntersectsLine( 1.0, 0.0, intersection ); // y = x
1649 BOOST_CHECK( intersects2 );
1650 BOOST_CHECK_EQUAL( intersection, VECTOR2I( 1500000, 1500000 ) ); // Midpoint
1651}
1652
1653BOOST_AUTO_TEST_CASE( IntersectLineZeroLengthSegments )
1654{
1655 // Test with zero-length segments (points)
1656
1657 VECTOR2I point( 10, 20 );
1658 SEG pointSeg( point, point );
1659 VECTOR2I intersection;
1660
1661 // Point lies on line
1662 bool intersects1 = pointSeg.IntersectsLine( 2.0, 0.0, intersection ); // y = 2x
1663 BOOST_CHECK( intersects1 ); // Point (10, 20) is on line y = 2x
1664 BOOST_CHECK_EQUAL( intersection, point );
1665
1666 // Point does not lie on line
1667 bool intersects2 = pointSeg.IntersectsLine( 3.0, 0.0, intersection ); // y = 3x
1668 BOOST_CHECK( !intersects2 ); // Point (10, 20) not on line y = 3x (would be y = 30)
1669
1670 // Point on horizontal line
1671 bool intersects3 = pointSeg.IntersectsLine( 0.0, 20.0, intersection ); // y = 20
1672 BOOST_CHECK( intersects3 );
1673 BOOST_CHECK_EQUAL( intersection, point );
1674}
1675
Definition seg.h:38
VECTOR2I A
Definition seg.h:45
ecoord SquaredDistance(const SEG &aSeg) const
Definition seg.cpp:76
bool IntersectsLine(double aSlope, double aOffset, VECTOR2I &aIntersection) const
Check if this segment intersects a line defined by slope aSlope and offset aOffset.
Definition seg.cpp:453
VECTOR2I::extended_type ecoord
Definition seg.h:40
VECTOR2I B
Definition seg.h:46
OPT_VECTOR2I Intersect(const SEG &aSeg, bool aIgnoreEndpoints=false, bool aLines=false) const
Compute intersection point of segment (this) with segment aSeg.
Definition seg.cpp:442
bool Collide(const SEG &aSeg, int aClearance, int *aActual=nullptr) const
Definition seg.cpp:538
bool ApproxParallel(const SEG &aSeg, int aDistanceThreshold=1) const
Definition seg.cpp:803
bool Collinear(const SEG &aSeg) const
Check if segment aSeg lies on the same line as (this).
Definition seg.h:282
SEG ParallelSeg(const VECTOR2I &aP) const
Compute a segment parallel to this one, passing through point aP.
Definition seg.cpp:529
bool ApproxPerpendicular(const SEG &aSeg) const
Definition seg.cpp:815
int Distance(const SEG &aSeg) const
Compute minimum Euclidean distance to segment aSeg.
Definition seg.cpp:698
SEG PerpendicularSeg(const VECTOR2I &aP) const
Compute a segment perpendicular to this one, passing through point aP.
Definition seg.cpp:520
const std::string Format() const
Return the vector formatted as a string.
Definition vector2d.h:419
EDA_ANGLE abs(const EDA_ANGLE &aAngle)
Definition eda_angle.h:400
A named data-driven test case.
Test cases for segment-line intersection.
Struct to hold general cases for collinearity, parallelism and perpendicularity.
Test cases for collisions (with clearance, for no clearance, it's just a SEG_SEG_DISTANCE_CASE of 0)
Test cases for segment intersection.
Struct to hold cases for operations with a SEG, and a VECTOR2I.
BOOST_AUTO_TEST_CASE(HorizontalAlignment)
BOOST_AUTO_TEST_SUITE(CadstarPartParser)
BOOST_REQUIRE(intersection.has_value()==c.ExpectedIntersection.has_value())
BOOST_AUTO_TEST_SUITE_END()
BOOST_TEST(netlist.find("R_G1 ARM_OUT1 DIE_B R='0.001 / ((SW_STATE)") !=std::string::npos)
BOOST_TEST_INFO("Two-port Series .op current = "<< iDevice)
BOOST_TEST_MESSAGE("\n=== Real-World Polygon PIP Benchmark ===\n"<< formatTable(table))
static const std::vector< SEG_SEG_BOOLEAN_CASE > seg_vec_perpendicular_cases
Test cases for perpendicularity.
BOOST_AUTO_TEST_CASE(EndpointCtorMod)
Checks whether the construction of a segment referencing external points works and that the endpoints...
static const std::vector< SEG_SEG_BOOLEAN_CASE > seg_vec_collinear_cases
Test cases for collinearity.
static const std::vector< SEG_VEC_CASE > segment_and_point_cases
Test cases to create segments passing through a point.
BOOST_DATA_TEST_CASE(SegSegPerpendicular, boost::unit_test::data::make(seg_vec_perpendicular_cases), c)
static const std::vector< SEG_SEG_DISTANCE_CASE > seg_seg_dist_cases
static const std::vector< SEG_VECTOR_DISTANCE_CASE > seg_vec_dist_cases
static const std::vector< SEG_LINE_INTERSECT_CASE > seg_line_intersect_cases
static const std::vector< SEG_SEG_INTERSECT_CASE > seg_intersect_cases
bool SegIntersectCorrect(const SEG_SEG_INTERSECT_CASE &aCase)
Predicate to check expected intersection between two segments.
static const std::vector< SEG_SEG_COLLIDE_CASE > seg_seg_coll_cases
bool SegLineIntersectCorrect(const SEG &aSeg, double aSlope, double aOffset, bool aExpIntersect, const VECTOR2I &aExpPoint=VECTOR2I())
Predicate to check expected intersection between a segment and an infinite line.
static const std::vector< SEG_SEG_BOOLEAN_CASE > seg_vec_parallel_cases
Test cases for parallelism.
BOOST_CHECK_PREDICATE(ArePolylineEndPointsNearCircle,(chain)(c.m_geom.m_center_point)(radius)(accuracy+epsilon))
BOOST_CHECK_EQUAL(result, "25.4")
VECTOR2< int32_t > VECTOR2I
Definition vector2d.h:683