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