| // 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/SweepLine.h" | |
| #include "modules/bentleyottmann/include/EventQueueInterface.h" | |
| #include "modules/bentleyottmann/include/Point.h" | |
| #include "tests/Test.h" | |
| #include <iterator> | |
| using namespace bentleyottmann; | |
| namespace bentleyottmann { | |
| struct SweepLineTestingPeer { | |
| SweepLineTestingPeer(SweepLine* sl) : fSL{sl} {} | |
| void verifySweepLine(int32_t y) const { | |
| fSL->verify(y); | |
| } | |
| void insertSegment(int i, const Segment& s) { | |
| auto& v = fSL->fSweepLine; | |
| v.insert(v.begin() + i, s); | |
| } | |
| size_t size() const { | |
| return fSL->fSweepLine.size(); | |
| } | |
| const std::vector<Segment>& sweepLine() const { | |
| return fSL->fSweepLine; | |
| } | |
| SweepLine* const fSL; | |
| }; | |
| } // namespace bentleyottmann | |
| using TP = SweepLineTestingPeer; | |
| DEF_TEST(BO_SweepLineSearch, reporter) { | |
| { | |
| SweepLine sweepLine; | |
| TP tp{&sweepLine}; | |
| Point p0 = {-100, -100}, | |
| p1 = { 100, 100}, | |
| p2 = { 100, -100}, | |
| p3 = {-100, 100}; | |
| Segment s0 = {p0, p1}, | |
| s1 = {p2, p3}; | |
| tp.insertSegment(1, s0); | |
| tp.insertSegment(2, s1); | |
| auto& sl = tp.sweepLine(); | |
| const Point crossing = {0, 0}; | |
| const auto l = std::lower_bound(sl.begin(), sl.end(), crossing, | |
| rounded_point_less_than_segment_in_x_lower); | |
| const auto r = std::lower_bound(l, sl.end(), crossing, | |
| rounded_point_less_than_segment_in_x_upper); | |
| // Remember that the index is off by 1 because of the left sentinel. | |
| REPORTER_ASSERT(reporter, std::distance(sl.begin(), l) == 1); | |
| REPORTER_ASSERT(reporter, std::distance(sl.begin(), r) == 3); | |
| } | |
| { | |
| SweepLine sweepLine; | |
| TP tp{&sweepLine}; | |
| // No longer cross at {0, 0}, but still round to {0, 0}. | |
| Point p0 = {-100, -100}, | |
| p1 = { 99, 100}, | |
| p2 = { 100, -100}, | |
| p3 = {-101, 100}; | |
| Segment s0 = {p0, p1}, | |
| s1 = {p2, p3}; | |
| tp.insertSegment(1, s0); | |
| tp.insertSegment(2, s1); | |
| auto& sl = tp.sweepLine(); | |
| const Point crossing = {0, 0}; | |
| const auto l = std::lower_bound(sl.begin(), sl.end(), crossing, | |
| rounded_point_less_than_segment_in_x_lower); | |
| const auto r = std::lower_bound(l, sl.end(), crossing, | |
| rounded_point_less_than_segment_in_x_upper); | |
| // Remember that the index is off by 1 because of the left sentinel. | |
| REPORTER_ASSERT(reporter, std::distance(sl.begin(), l) == 1); | |
| REPORTER_ASSERT(reporter, std::distance(sl.begin(), r) == 3); | |
| } | |
| } | |
| DEF_TEST(BO_SweepLineInsert, reporter) { | |
| { | |
| SweepLine sweepLine; | |
| TP tp{&sweepLine}; | |
| tp.verifySweepLine(0); | |
| } | |
| { // Handle insert | |
| SweepLine sweepLine; | |
| TP tp{&sweepLine}; | |
| class FailIfEventQueueAdd : public EventQueueInterface { | |
| public: | |
| void addCrossing(Point crossingPoint, const Segment& s0, const Segment& s1) override { | |
| SK_ABORT("There should be no crossings."); | |
| } | |
| } eventQueue; | |
| InsertionSegmentSet insertions; | |
| Segment s = {{0, -100}, {0, 100}}; | |
| insertions.insert(s); | |
| tp.verifySweepLine(-99); | |
| sweepLine.handleInsertionsAndCheckForNewCrossings( | |
| Point{0, -100}, insertions, &eventQueue); | |
| REPORTER_ASSERT(reporter, tp.size() == 3); | |
| tp.verifySweepLine(-99); | |
| } | |
| { // Handle 3 segments where removing middle segment introduces crossing | |
| SweepLine sweepLine; | |
| TP tp{&sweepLine}; | |
| Point p0 = {-100, -100}, | |
| p1 = { 100, 100}, | |
| p2 = { 100, -100}, | |
| p3 = {-100, 100}, | |
| p4 = { 0, -100}, | |
| p5 = { 0, -50}; | |
| Segment s0 = {p0, p1}, | |
| s1 = {p2, p3}, | |
| s2 = {p4, p5}; | |
| class CollectCrossings : public EventQueueInterface { | |
| public: | |
| void addCrossing(Point crossingPoint, const Segment& s0, const Segment& s1) override { | |
| fCrossing.push_back({s0, s1, crossingPoint}); | |
| } | |
| std::vector<Crossing> fCrossing; | |
| } eventQueue; | |
| { // Simulate handling Upper s0 | |
| InsertionSegmentSet insertions; | |
| insertions.insert(s0); | |
| tp.verifySweepLine(-99); | |
| sweepLine.handleInsertionsAndCheckForNewCrossings( | |
| p0, insertions, &eventQueue); | |
| REPORTER_ASSERT(reporter, tp.size() == 3); | |
| REPORTER_ASSERT(reporter, eventQueue.fCrossing.size() == 0); | |
| tp.verifySweepLine(-99); | |
| } | |
| { // Simulate handling Upper s2 | |
| InsertionSegmentSet insertions; | |
| insertions.insert(s2); | |
| tp.verifySweepLine(-99); | |
| sweepLine.handleInsertionsAndCheckForNewCrossings( | |
| p4, insertions, &eventQueue); | |
| REPORTER_ASSERT(reporter, tp.size() == 4); | |
| REPORTER_ASSERT(reporter, eventQueue.fCrossing.size() == 0); | |
| tp.verifySweepLine(-99); | |
| } | |
| { // Simulate handling Upper s1 | |
| InsertionSegmentSet insertions; | |
| insertions.insert(s1); | |
| tp.verifySweepLine(-99); | |
| sweepLine.handleInsertionsAndCheckForNewCrossings( | |
| p2, insertions, &eventQueue); | |
| REPORTER_ASSERT(reporter, tp.size() == 5); | |
| REPORTER_ASSERT(reporter, eventQueue.fCrossing.size() == 0); | |
| tp.verifySweepLine(-99); | |
| } | |
| { // Simulate handling Lower s2 which introduces a crossing | |
| DeletionSegmentSet deletions; // empty set because this will be a lower event | |
| InsertionSegmentSet insertions; | |
| tp.verifySweepLine(-51); | |
| sweepLine.handleDeletions(p5, deletions); | |
| REPORTER_ASSERT(reporter, tp.size() == 4); | |
| sweepLine.handleInsertionsAndCheckForNewCrossings( | |
| p5, insertions, &eventQueue); | |
| REPORTER_ASSERT(reporter, eventQueue.fCrossing.size() == 1); | |
| tp.verifySweepLine(-51); | |
| } | |
| { // Handle crossing | |
| DeletionSegmentSet deletions{s0, s1}; // empty set because this will be a lower event | |
| InsertionSegmentSet insertions{s0, s1}; | |
| tp.verifySweepLine(-1); // Check above the crossing | |
| sweepLine.handleDeletions({0,0}, deletions); | |
| sweepLine.handleInsertionsAndCheckForNewCrossings( | |
| {0,0}, insertions, &eventQueue); | |
| REPORTER_ASSERT(reporter, tp.size() == 4); | |
| REPORTER_ASSERT(reporter, eventQueue.fCrossing.size() == 1); | |
| tp.verifySweepLine(1); // Make sure things are correct after deletion | |
| } | |
| { // Handle deletion s1 | |
| DeletionSegmentSet deletions{}; // empty set because this will be a lower event | |
| InsertionSegmentSet insertions{}; | |
| tp.verifySweepLine(99); // Check above the crossing | |
| sweepLine.handleDeletions(p3, deletions); | |
| sweepLine.handleInsertionsAndCheckForNewCrossings( | |
| p3, insertions, &eventQueue); | |
| REPORTER_ASSERT(reporter, tp.size() == 3); | |
| REPORTER_ASSERT(reporter, eventQueue.fCrossing.size() == 1); | |
| tp.verifySweepLine(99); // Make sure sentinels are correct. | |
| } | |
| { // Handle deletion s0 | |
| DeletionSegmentSet deletions{}; // empty set because this will be a lower event | |
| InsertionSegmentSet insertions{}; | |
| tp.verifySweepLine(99); // Check above the crossing | |
| sweepLine.handleDeletions(p1, deletions); | |
| sweepLine.handleInsertionsAndCheckForNewCrossings( | |
| p1, insertions, &eventQueue); | |
| REPORTER_ASSERT(reporter, tp.size() == 2); | |
| REPORTER_ASSERT(reporter, eventQueue.fCrossing.size() == 1); | |
| tp.verifySweepLine(99); // Make sure sentinels are correct. | |
| } | |
| } | |
| } |