| // Copyright 2023 Google LLC | |
| // Use of this source code is governed by a BSD-style license that can be found in the LICENSE file. | |
| #include "modules/bentleyottmann/include/Segment.h" | |
| #include "tests/Test.h" | |
| using namespace bentleyottmann; | |
| DEF_TEST(BO_SegmentBasic, reporter) { | |
| { | |
| Segment s = {{0, 0}, {1, 1}}; | |
| REPORTER_ASSERT(reporter, s.upper() == s.p0); | |
| REPORTER_ASSERT(reporter, s.lower() == s.p1); | |
| } | |
| { | |
| Segment s = {{1, 0}, {0, 1}}; | |
| REPORTER_ASSERT(reporter, s.upper() == s.p0); | |
| REPORTER_ASSERT(reporter, s.lower() == s.p1); | |
| } | |
| { | |
| Segment s = {{1, 1}, {0, 0}}; | |
| REPORTER_ASSERT(reporter, s.upper() == s.p1); | |
| REPORTER_ASSERT(reporter, s.lower() == s.p0); | |
| } | |
| { | |
| Segment s = {{0, 1}, {1, 0}}; | |
| REPORTER_ASSERT(reporter, s.upper() == s.p1); | |
| REPORTER_ASSERT(reporter, s.lower() == s.p0); | |
| } | |
| } | |
| static Segment swap_ends(const Segment& s) { | |
| return {s.p1, s.p0}; | |
| } | |
| DEF_TEST(BO_no_intersection_bounding_box, reporter) { | |
| Segment interesting[] = {{Point::Smallest(), Point::Smallest()+ Point{10, 5}}, | |
| {Point::Largest(), Point::Largest() - Point{10, 5}}, | |
| {{-10, -5}, {10, 5}}}; | |
| // Intersection | |
| for (auto& s0 : interesting) { | |
| auto [l, t, r, b] = s0.bounds(); | |
| // Points in the interior of interesting rectangles | |
| for(Point p : {Point {l + 1, t + 1}, | |
| Point {r - 1, t + 1}, | |
| Point {r - 1, b - 1}, | |
| Point {l + 1, b - 1}}) { | |
| Segment s1 = {p, {0, 0}}; | |
| REPORTER_ASSERT(reporter, !no_intersection_by_bounding_box(s0, s1)); | |
| REPORTER_ASSERT(reporter, !no_intersection_by_bounding_box(s1, s0)); | |
| REPORTER_ASSERT(reporter, | |
| !no_intersection_by_bounding_box(swap_ends(s0), swap_ends(s1))); | |
| REPORTER_ASSERT(reporter, | |
| !no_intersection_by_bounding_box(swap_ends(s0), swap_ends(s1))); | |
| } | |
| } | |
| int32_t small = Point::Smallest().x, | |
| big = Point::Largest().x; | |
| // No Intersection | |
| for (auto& s0 : interesting) { | |
| auto [l, t, r, b] = s0.bounds(); | |
| Segment outside[] = {{{r, t}, {big, b}}, | |
| {{r, b}, {big, big}}, | |
| {{l, b}, {r, big}}, | |
| {{l, b}, {small, big}}, | |
| {{l, t}, {small, b}}, | |
| {{l, t}, {small, small}}, | |
| {{l, t}, {r, small}}, | |
| {{r, t}, {small, small}}}; | |
| for (auto& s1 : outside) { | |
| REPORTER_ASSERT(reporter, no_intersection_by_bounding_box(s0, s1)); | |
| REPORTER_ASSERT(reporter, no_intersection_by_bounding_box(s1, s0)); | |
| REPORTER_ASSERT(reporter, | |
| no_intersection_by_bounding_box(swap_ends(s0), swap_ends(s1))); | |
| REPORTER_ASSERT(reporter, | |
| no_intersection_by_bounding_box(swap_ends(s0), swap_ends(s1))); | |
| } | |
| } | |
| } | |
| DEF_TEST(BO_intersectBasic, reporter) { | |
| auto checkIntersection = [reporter](Segment s0, Segment s1, Point expected) { | |
| { | |
| auto answer = intersect(s0, s1); | |
| REPORTER_ASSERT(reporter, answer.has_value()); | |
| REPORTER_ASSERT(reporter, answer.value() == expected); | |
| } | |
| { | |
| auto answer = intersect(s1, s0); | |
| REPORTER_ASSERT(reporter, answer.has_value()); | |
| REPORTER_ASSERT(reporter, answer.value() == expected); | |
| } | |
| { | |
| auto answer = intersect(swap_ends(s0), swap_ends(s1)); | |
| REPORTER_ASSERT(reporter, answer.has_value()); | |
| REPORTER_ASSERT(reporter, answer.value() == expected); | |
| } | |
| { | |
| auto answer = intersect(swap_ends(s1), swap_ends(s0)); | |
| REPORTER_ASSERT(reporter, answer.has_value()); | |
| REPORTER_ASSERT(reporter, answer.value() == expected); | |
| } | |
| }; | |
| { | |
| Segment s0 = {{-1, 0}, {1, 0}}, | |
| s1 = {{ 0, 1}, {0, -1}}; | |
| checkIntersection(s0, s1, Point{0, 0}); | |
| } | |
| { | |
| Segment s0 = {{-1, 0}, {5, 0}}, | |
| s1 = {{ 0, 1}, {0, -1}}; | |
| checkIntersection(s0, s1, Point{0, 0}); | |
| } | |
| { | |
| Segment s0 = {{5, 0}, {-1, 0}}, | |
| s1 = {{ 0, -1}, {0, 1}}; | |
| checkIntersection(s0, s1, Point{0, 0}); | |
| } | |
| { | |
| Segment s0 = {{-5, -5}, {5, 5}}, | |
| s1 = {{-5, 5}, {5, -5}}; | |
| checkIntersection(s0, s1, Point{0, 0}); | |
| } | |
| // Test very close segments (x0, 0) -> (x1, 1) & (x2, 0) -> (x3, 1) | |
| for (int32_t x0 = -10; x0 <= 10; x0++) { | |
| for (int32_t x1 = -10; x1 <= 10; x1++) { | |
| for (int32_t x2 = -10; x2 <= 10; x2++) { | |
| for (int32_t x3 = -10; x3 <= 10; x3++) { | |
| Point P0 = {x0, 0}, | |
| P1 = {x1, 1}, | |
| P2 = {x2, 0}, | |
| P3 = {x3, 1}; | |
| auto actual = intersect({P0, P1}, {P2, P3}); | |
| bool expected = (x0 < x2 && x3 < x1) || (x2 < x0 && x1 < x3); | |
| REPORTER_ASSERT(reporter, actual.has_value() == expected); | |
| if (actual) { | |
| int32_t y = std::abs(x2 - x0) >= std::abs(x3 - x1); | |
| REPORTER_ASSERT(reporter, actual.value().y == y); | |
| } | |
| } | |
| } | |
| } | |
| } | |
| { | |
| Segment s0 = {{0, -100}, {0, -50}}, | |
| s1 = {{100, -100}, {-100, 100}}; // goes through (0,0) | |
| auto answer = intersect(s0, s1); | |
| REPORTER_ASSERT(reporter, !answer.has_value()); | |
| answer = intersect(s1, s0); | |
| REPORTER_ASSERT(reporter, !answer.has_value()); | |
| } | |
| { | |
| Segment s0 = {{0, 100}, {0, 50}}, | |
| s1 = {{100, -100}, {-100, 100}}; // goes through (0,0) | |
| auto answer = intersect(s0, s1); | |
| REPORTER_ASSERT(reporter, !answer.has_value()); | |
| answer = intersect(s1, s0); | |
| REPORTER_ASSERT(reporter, !answer.has_value()); | |
| } | |
| } | |
| DEF_TEST(BO_lessAtBasic, reporter) { | |
| { // Parallel lines | |
| Segment s0 = {{-1, 1}, {-1, -1}}, | |
| s1 = {{1, 1}, {1, -1}}; | |
| REPORTER_ASSERT(reporter, less_than_at(s0, s1, -1)); | |
| REPORTER_ASSERT(reporter, !less_than_at(s1, s0, -1)); | |
| REPORTER_ASSERT(reporter, less_than_at(s0, s1, 0)); | |
| REPORTER_ASSERT(reporter, !less_than_at(s1, s0, 0)); | |
| REPORTER_ASSERT(reporter, less_than_at(s0, s1, 1)); | |
| REPORTER_ASSERT(reporter, !less_than_at(s1, s0, 1)); | |
| } | |
| { // Crossed lines | |
| Segment s0 = {{-1, -1}, {1, 1}}, | |
| s1 = {{1, -1}, {-1, 1}}; | |
| REPORTER_ASSERT(reporter, less_than_at(s0, s1, -1)); | |
| REPORTER_ASSERT(reporter, !less_than_at(s1, s0, -1)); | |
| // When they are == neither is less. | |
| REPORTER_ASSERT(reporter, !less_than_at(s0, s1, 0)); | |
| REPORTER_ASSERT(reporter, !less_than_at(s1, s0, 0)); | |
| REPORTER_ASSERT(reporter, !less_than_at(s0, s1, 1)); | |
| REPORTER_ASSERT(reporter, less_than_at(s1, s0, 1)); | |
| } | |
| { // Near crossing | |
| Segment s0 = {{0, -100}, {0, 100}}, | |
| s1 = {{-3, 98}, {3, 104}}; | |
| REPORTER_ASSERT(reporter, !less_than_at(s0, s1, 98)); | |
| REPORTER_ASSERT(reporter, less_than_at(s1, s0, 98)); | |
| REPORTER_ASSERT(reporter, !less_than_at(s0, s1, 99)); | |
| REPORTER_ASSERT(reporter, less_than_at(s1, s0, 99)); | |
| REPORTER_ASSERT(reporter, !less_than_at(s0, s1, 100)); | |
| REPORTER_ASSERT(reporter, less_than_at(s1, s0, 100)); | |
| } | |
| } | |
| DEF_TEST(BO_compareSlopesBasic, reporter) { | |
| { // Both horizontal | |
| Segment s0 = {{-1, 1}, {0, 1}}, | |
| s1 = {{-2, 1}, {1, 1}}; | |
| REPORTER_ASSERT(reporter, compare_slopes(s0, s1) == 0); | |
| REPORTER_ASSERT(reporter, compare_slopes(s1, s0) == 0); | |
| } | |
| { // One horizontal | |
| Segment s0 = {{-1, 1}, {0, 0}}, | |
| s1 = {{-2, 1}, {1, 1}}; | |
| REPORTER_ASSERT(reporter, compare_slopes(s0, s1) == -1); | |
| REPORTER_ASSERT(reporter, compare_slopes(s1, s0) == 1); | |
| } | |
| { // One vertical | |
| Segment s0 = {{-1, 1}, {-1, 0}}, // Vertical | |
| s1 = {{-2, 1}, {-1, 0}}, | |
| s2 = {{2, 1}, {-1, 0}}; | |
| REPORTER_ASSERT(reporter, compare_slopes(s0, s1) == 1); | |
| REPORTER_ASSERT(reporter, compare_slopes(s1, s0) == -1); | |
| REPORTER_ASSERT(reporter, compare_slopes(s0, s2) == -1); | |
| REPORTER_ASSERT(reporter, compare_slopes(s2, s0) == 1); | |
| } | |
| { // Equal slope | |
| Segment s0 = {{-2, 1}, {0, 0}}, | |
| s1 = {{-4, 2}, {0, 0}}; | |
| REPORTER_ASSERT(reporter, compare_slopes(s0, s1) == 0); | |
| REPORTER_ASSERT(reporter, compare_slopes(s1, s0) == 0); | |
| } | |
| { // Equal slope | |
| Segment s0 = {{2, 1}, {0, 0}}, | |
| s1 = {{4, 2}, {0, 0}}; | |
| REPORTER_ASSERT(reporter, compare_slopes(s0, s1) == 0); | |
| REPORTER_ASSERT(reporter, compare_slopes(s1, s0) == 0); | |
| } | |
| { | |
| Segment s0 = {{-2, 1}, {0, 0}}, | |
| s1 = {{4, 2}, {0, 0}}; | |
| REPORTER_ASSERT(reporter, compare_slopes(s0, s1) == -1); | |
| REPORTER_ASSERT(reporter, compare_slopes(s1, s0) == 1); | |
| } | |
| { | |
| Segment s0 = {{-2, 1}, {0, 0}}, | |
| s1 = {{-3, 1}, {0, 0}}; | |
| REPORTER_ASSERT(reporter, compare_slopes(s0, s1) == 1); | |
| REPORTER_ASSERT(reporter, compare_slopes(s1, s0) == -1); | |
| } | |
| } | |
| DEF_TEST(BO_rounded_point_less_than_segment_in_x_lower, reporter) { | |
| { // Vertical segment | |
| Segment s = {{3, -50}, {3, 50}}; | |
| REPORTER_ASSERT(reporter, rounded_point_less_than_segment_in_x_lower(s, {4, 7})); | |
| REPORTER_ASSERT(reporter, !rounded_point_less_than_segment_in_x_lower(s, {3, 7})); | |
| REPORTER_ASSERT(reporter, !rounded_point_less_than_segment_in_x_lower(s, {2, 7})); | |
| } | |
| { // Horizontal segment | |
| Segment s = {{-10, 3}, {10, 3}}; | |
| REPORTER_ASSERT(reporter, rounded_point_less_than_segment_in_x_lower(s, {11, 3})); | |
| REPORTER_ASSERT(reporter, !rounded_point_less_than_segment_in_x_lower(s, {10, 3})); | |
| REPORTER_ASSERT(reporter, !rounded_point_less_than_segment_in_x_lower(s, {4, 3})); | |
| REPORTER_ASSERT(reporter, !rounded_point_less_than_segment_in_x_lower(s, {-10, 3})); | |
| REPORTER_ASSERT(reporter, !rounded_point_less_than_segment_in_x_lower(s, {-11, 3})); | |
| } | |
| { // Pass through {0, 0} | |
| Segment s = {{-100, -100}, {100, 100}}; | |
| REPORTER_ASSERT(reporter, rounded_point_less_than_segment_in_x_lower(s, {1, 0})); | |
| REPORTER_ASSERT(reporter, !rounded_point_less_than_segment_in_x_lower(s, {0, 0})); | |
| REPORTER_ASSERT(reporter, !rounded_point_less_than_segment_in_x_lower(s, {-1, 0})); | |
| } | |
| { // Just left of {0, 0}, but still rounds to {0, 0} | |
| Segment s = {{-100, -100}, {199, 200}}; | |
| REPORTER_ASSERT(reporter, rounded_point_less_than_segment_in_x_lower(s, {1, 0})); | |
| REPORTER_ASSERT(reporter, !rounded_point_less_than_segment_in_x_lower(s, {0, 0})); | |
| REPORTER_ASSERT(reporter, !rounded_point_less_than_segment_in_x_lower(s, {-1, 0})); | |
| } | |
| { // Just right of {0, 0}, but still rounds to {0, 0} | |
| Segment s = {{-100, -100}, {201, 200}}; | |
| REPORTER_ASSERT(reporter, rounded_point_less_than_segment_in_x_lower(s, {1, 0})); | |
| REPORTER_ASSERT(reporter, !rounded_point_less_than_segment_in_x_lower(s, {0, 0})); | |
| REPORTER_ASSERT(reporter, !rounded_point_less_than_segment_in_x_lower(s, {-1, 0})); | |
| } | |
| } | |
| DEF_TEST(BO_rounded_point_less_than_segment_in_x_upper, reporter) { | |
| { // Vertical segment | |
| Segment s = {{3, -50}, {3, 50}}; | |
| REPORTER_ASSERT(reporter, rounded_point_less_than_segment_in_x_upper(s, {4, 7})); | |
| REPORTER_ASSERT(reporter, rounded_point_less_than_segment_in_x_upper(s, {3, 7})); | |
| REPORTER_ASSERT(reporter, !rounded_point_less_than_segment_in_x_upper(s, {2, 7})); | |
| } | |
| { // Horizontal segment | |
| Segment s = {{-10, 3}, {10, 3}}; | |
| REPORTER_ASSERT(reporter, rounded_point_less_than_segment_in_x_upper(s, {11, 3})); | |
| REPORTER_ASSERT(reporter, rounded_point_less_than_segment_in_x_upper(s, {10, 3})); | |
| REPORTER_ASSERT(reporter, !rounded_point_less_than_segment_in_x_upper(s, {4, 3})); | |
| REPORTER_ASSERT(reporter, !rounded_point_less_than_segment_in_x_upper(s, {-10, 3})); | |
| REPORTER_ASSERT(reporter, !rounded_point_less_than_segment_in_x_upper(s, {-11, 3})); | |
| } | |
| { // Pass through {0, 0} | |
| Segment s = {{-100, -100}, {100, 100}}; | |
| REPORTER_ASSERT(reporter, rounded_point_less_than_segment_in_x_upper(s, {1, 0})); | |
| REPORTER_ASSERT(reporter, rounded_point_less_than_segment_in_x_upper(s, {0, 0})); | |
| REPORTER_ASSERT(reporter, !rounded_point_less_than_segment_in_x_upper(s, {-1, 0})); | |
| } | |
| { // Just left of {0, 0}, but still rounds to {0, 0} | |
| Segment s = {{-100, -100}, {199, 200}}; | |
| REPORTER_ASSERT(reporter, rounded_point_less_than_segment_in_x_upper(s, {1, 0})); | |
| REPORTER_ASSERT(reporter, rounded_point_less_than_segment_in_x_upper(s, {0, 0})); | |
| REPORTER_ASSERT(reporter, !rounded_point_less_than_segment_in_x_upper(s, {-1, 0})); | |
| } | |
| { // Just right of {0, 0}, but still rounds to {0, 0} | |
| Segment s = {{-100, -100}, {201, 200}}; | |
| REPORTER_ASSERT(reporter, rounded_point_less_than_segment_in_x_upper(s, {1, 0})); | |
| REPORTER_ASSERT(reporter, rounded_point_less_than_segment_in_x_upper(s, {0, 0})); | |
| REPORTER_ASSERT(reporter, !rounded_point_less_than_segment_in_x_upper(s, {-1, 0})); | |
| } | |
| } |