From 0c49eb58452881b50151c29e6208da2df20f5915 Mon Sep 17 00:00:00 2001 From: Danny Robson Date: Thu, 7 Nov 2024 12:32:13 +1000 Subject: [PATCH] geom/segment: add segment-segment intersection test --- cruft/util/geom/segment.cpp | 66 +++++++++++++++++++++++++++++++++++++ test/geom/segment.cpp | 62 +++++++++++++++++++++++++++++++--- 2 files changed, 124 insertions(+), 4 deletions(-) diff --git a/cruft/util/geom/segment.cpp b/cruft/util/geom/segment.cpp index 48974ce9..3e000bd1 100644 --- a/cruft/util/geom/segment.cpp +++ b/cruft/util/geom/segment.cpp @@ -69,6 +69,72 @@ cruft::geom::intersects (cruft::geom::segment2i seg, cruft::region2i rect) } +/////////////////////////////////////////////////////////////////////////////// +template <> +bool +cruft::geom::intersects (cruft::geom::segment2f ab, cruft::point2f p) +{ + auto const hi = max (ab.a, ab.b); + auto const lo = min (ab.a, ab.b); + + return all (p <= hi) and all (p >= lo); +} + + +/////////////////////////////////////////////////////////////////////////////// +// segment-segment intersection in 2D is derived from +// https://www.geeksforgeeks.org/check-if-two-given-line-segments-intersect +// +// The idea is to check the winding of 4 triangles created by the segment. +// +// Co-linear segments need some additional tests to determine if points of one segment lie on the other. + + +/// \return 0 if colinear, 1 for clockwise, -1 for counter-clockwise +static int +winding ( + cruft::point2f const p, + cruft::point2f const q, + cruft::point2f const r +) { + auto const qp = q - p; + auto const rq = r - q; + + auto const c = cross (qp, rq); + + if (cruft::exactly_zero (c)) + return 0; + else if (c > 0) + return 1; + else + return -1; +} + + +//----------------------------------------------------------------------------- +template <> +bool +cruft::geom::intersects ( + cruft::geom::segment2f const s0, + cruft::geom::segment2f const s1 +) { + auto const w0 = winding (s0.a, s0.b, s1.a); + auto const w1 = winding (s0.a, s0.b, s1.b); + auto const w2 = winding (s1.a, s1.b, s0.a); + auto const w3 = winding (s1.a, s1.b, s0.b); + + if (w0 != w1 and w2 != w3) + return true; + + if (w0 == 0 and intersects (s0, s1.a)) return true; + if (w1 == 0 and intersects (s0, s1.b)) return true; + if (w2 == 0 and intersects (s1, s0.a)) return true; + if (w3 == 0 and intersects (s1, s0.b)) return true; + + return false; +} + + /////////////////////////////////////////////////////////////////////////////// template <> cruft::geom::aabb3f diff --git a/test/geom/segment.cpp b/test/geom/segment.cpp index d3868195..e1d23a15 100644 --- a/test/geom/segment.cpp +++ b/test/geom/segment.cpp @@ -6,7 +6,8 @@ /////////////////////////////////////////////////////////////////////////////// -void test_point_distance (cruft::TAP::logger &tap) +void +test_point_distance (cruft::TAP::logger &tap) { static struct { cruft::point3f a, b; @@ -27,8 +28,9 @@ void test_point_distance (cruft::TAP::logger &tap) } -//----------------------------------------------------------------------------- -void test_region_intersection (cruft::TAP::logger &tap) +/////////////////////////////////////////////////////////////////////////////// +void +test_region_intersection (cruft::TAP::logger &tap) { using p = cruft::point2i; @@ -70,7 +72,58 @@ void test_region_intersection (cruft::TAP::logger &tap) /////////////////////////////////////////////////////////////////////////////// -void test_bresenham (cruft::TAP::logger &tap) +void +test_segment_intersection (cruft::TAP::logger &tap) +{ + using cruft::geom::segment2f; + using cruft::point2f; + + static struct { + segment2f a; + segment2f b; + bool expected; + char const *message; + } TESTS[] = { + { + .a = { { 1, 1 }, { 10, 1 } }, + .b = { { 1, 2 }, { 10, 2 } }, + .expected = false, + .message = "parallel" + }, + { + .a = { { 10, 0 }, { 0, 10 } }, + .b = { { 0, 0 }, { 10, 10 } }, + .expected = true, + .message = "right angles" + }, + { + .a = { { -5, -5 }, { 0, 0 } }, + .b = { { 1, 1 }, { 10, 10 } }, + .expected = false, + .message = "co-linear stopping early", + }, + { + .a = { { -5, -5 }, { 5, 5 } }, + .b = { { 0, 0 }, { 10, 10 } }, + .expected = true, + .message = "co-linear stopping midway", + }, + }; + + for (auto const &t: TESTS) { + tap.expect_eq ( + t.expected, + intersects (t.a, t.b), + "segment-segment {}", + t.message + ); + } +} + + +/////////////////////////////////////////////////////////////////////////////// +void +test_bresenham (cruft::TAP::logger &tap) { static struct { cruft::point2i a; @@ -120,6 +173,7 @@ main (int, char**) cruft::TAP::logger tap; test_point_distance (tap); test_region_intersection (tap); + test_segment_intersection (tap); test_bresenham (tap); return tap.status (); } \ No newline at end of file