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