| // 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/EventQueue.h" |
| #include "tests/Test.h" |
| |
| namespace bentleyottmann { |
| class EventQueueTestingPeer { |
| public: |
| static Event NextEvent(EventQueue* eq) { |
| SkASSERT(eq->hasMoreEvents()); |
| |
| auto firstElement = eq->fQueue.begin(); |
| |
| // Extract event at the beginning of the queue. |
| Event event = *firstElement; |
| |
| // Remove the beginning element from the queue. |
| eq->fQueue.erase(firstElement); |
| |
| return event; |
| } |
| }; |
| } // namespace bentleyottmann |
| |
| using namespace bentleyottmann; |
| |
| DEF_TEST(BO_EventQueueOrdering, reporter) { |
| { // Check that event types are ordered correctly. |
| EventQueue::Queue q; |
| |
| // Insert the events in reverse order. |
| Point eventPoint = {100, 100}; |
| Segment s = {{100, 100}, {200, 200}}; |
| q.insert(Event{eventPoint, Upper{s}}); |
| Segment s0 = {{50, 50}, {150, 150}}, |
| s1 = {{150, 50}, {50, 150}}; |
| q.insert(Event{eventPoint, Cross{s0, s1}}); |
| q.insert(Event{eventPoint, Lower{}}); |
| |
| // Make sure that the events are in the right order. |
| auto cursor = q.begin(); |
| REPORTER_ASSERT(reporter, std::holds_alternative<Lower>(cursor->type)); |
| ++cursor; |
| REPORTER_ASSERT(reporter, std::holds_alternative<Cross>(cursor->type)); |
| ++cursor; |
| REPORTER_ASSERT(reporter, std::holds_alternative<Upper>(cursor->type)); |
| } |
| } |
| |
| DEF_TEST(BO_EventQueueBasic, reporter) { |
| { |
| EventQueue::Queue q; |
| EventQueue eq{std::move(q)}; |
| REPORTER_ASSERT(reporter, !eq.hasMoreEvents()); |
| } |
| { |
| EventQueue::Queue q; |
| Point eventPoint = {100, 100}; |
| q.insert({eventPoint, Lower{} }); |
| EventQueue eq{std::move(q)}; |
| { |
| REPORTER_ASSERT(reporter, eq.hasMoreEvents()); |
| Event e = EventQueueTestingPeer::NextEvent(&eq); |
| REPORTER_ASSERT(reporter, e.where == eventPoint); |
| REPORTER_ASSERT(reporter, !eq.hasMoreEvents()); |
| } |
| } |
| { // Check that Lower events are de-duplicated. |
| EventQueue::Queue q; |
| Point eventPoint = {100, 100}; |
| q.insert({eventPoint, Lower{}}); |
| q.insert({eventPoint, Lower{}}); |
| EventQueue eq{std::move(q)}; |
| { |
| // There should be only one lower because of queue de-duplication |
| REPORTER_ASSERT(reporter, eq.hasMoreEvents()); |
| auto [p, _] = EventQueueTestingPeer::NextEvent(&eq); |
| REPORTER_ASSERT(reporter, p == eventPoint); |
| REPORTER_ASSERT(reporter, !eq.hasMoreEvents()); |
| } |
| } |
| { // Check that Lower distinct Lower events are distinct. |
| EventQueue::Queue q; |
| Point eventPoint1 = {100, 100}; |
| Point eventPoint2 = {100, 101}; |
| |
| q.insert({eventPoint1, Lower{}}); |
| q.insert({eventPoint2, Lower{}}); |
| EventQueue eq{std::move(q)}; |
| { |
| // There should be only one lower because of queue de-duplication |
| REPORTER_ASSERT(reporter, eq.hasMoreEvents()); |
| auto [p, _] = EventQueueTestingPeer::NextEvent(&eq); |
| REPORTER_ASSERT(reporter, p == eventPoint1); |
| } |
| { |
| // There should be only one lower because of queue de-duplication |
| REPORTER_ASSERT(reporter, eq.hasMoreEvents()); |
| auto [p, _] = EventQueueTestingPeer::NextEvent(&eq); |
| REPORTER_ASSERT(reporter, p == eventPoint2); |
| REPORTER_ASSERT(reporter, !eq.hasMoreEvents()); |
| } |
| } |
| { // Check that non-Lower events are separate. |
| EventQueue::Queue q; |
| Segment s0 {{0, 0}, {100, 100}}; |
| Segment s1 {{0, 0}, {-100, 100}}; |
| q.insert({Point{0, 0}, Upper{s0}}); |
| q.insert({Point{0, 0}, Upper{s1}}); |
| EventQueue eq{std::move(q)}; |
| { |
| REPORTER_ASSERT(reporter, eq.hasMoreEvents()); |
| Event e = EventQueueTestingPeer::NextEvent(&eq); |
| Point upperPt = Point{0, 0}; |
| REPORTER_ASSERT(reporter, e.where == upperPt); |
| REPORTER_ASSERT(reporter, e.type.index() == 2); |
| Upper upper = std::get<Upper>(e.type); |
| REPORTER_ASSERT(reporter, !(upper < Upper{s1}) && !(Upper{s1} < upper)); |
| Event e2 = EventQueueTestingPeer::NextEvent(&eq); |
| REPORTER_ASSERT(reporter, e2.where == upperPt); |
| REPORTER_ASSERT(reporter, e2.type.index() == 2); |
| Upper upper2 = std::get<Upper>(e2.type); |
| REPORTER_ASSERT(reporter, !(upper2 < Upper{s0}) && !(Upper{s0} < upper2)); |
| REPORTER_ASSERT(reporter, !eq.hasMoreEvents()); |
| } |
| } |
| } |
| |
| enum HasDeletions { |
| kHasDeletions, // The handleDeletions call should be called. |
| kHasNoDeletions // The handleDeletions call should not be called. |
| }; |
| |
| struct TestEventHandler : public SweepLineInterface { |
| TestEventHandler(skiatest::Reporter*r, |
| Point eventPoint, |
| SkSpan<const Segment> deletions, |
| SkSpan<const Segment> insertions, |
| SkSpan<const Crossing> crossings, |
| HasDeletions hasDeletions = kHasDeletions) |
| : fR(r) |
| , fCandidateEventPoint{eventPoint} |
| , fDeletions{deletions.begin(), deletions.end()} |
| , fInsertions{insertions.begin(), insertions.end()} |
| , fCrossings{crossings.begin(), crossings.end()} |
| , fHasDeletions{hasDeletions} {} |
| |
| void handleDeletions(Point eventPoint, |
| const DeletionSegmentSet& removing) override { |
| if (fHasDeletions == kHasNoDeletions) { |
| REPORTER_ASSERT(fR, false, "There should be no deletions."); |
| return; |
| } |
| |
| REPORTER_ASSERT(fR, eventPoint == fCandidateEventPoint); |
| |
| REPORTER_ASSERT(fR, removing.size() == fDeletions.size()); |
| |
| for (const Segment& s : fDeletions) { |
| REPORTER_ASSERT(fR, removing.find(s) != removing.end()); |
| } |
| } |
| |
| void |
| handleInsertionsAndCheckForNewCrossings(Point eventPoint, |
| const InsertionSegmentSet& inserting, |
| EventQueueInterface* queue) override { |
| REPORTER_ASSERT(fR, eventPoint == fCandidateEventPoint); |
| |
| REPORTER_ASSERT(fR, inserting.size() == fInsertions.size()); |
| |
| for (const Segment& s : fInsertions) { |
| REPORTER_ASSERT(fR, inserting.find(s) != inserting.end()); |
| } |
| |
| for (const Crossing& crossing : fCrossings) { |
| auto [s0, s1, pt] = crossing; |
| queue->addCrossing(pt, s0, s1); |
| } |
| } |
| |
| skiatest::Reporter* const fR; |
| const Point fCandidateEventPoint; |
| std::vector<Segment> fDeletions; |
| std::vector<Segment> fInsertions; |
| std::vector<Crossing> fCrossings; |
| const HasDeletions fHasDeletions; |
| }; |
| |
| DEF_TEST(BO_EventQueueHandlerInterface, reporter) { |
| { // Check that a Lower event is added while processing the Upper event. |
| EventQueue::Queue q; |
| static constexpr Point eventPoint = {100, 100}; |
| static constexpr Point endPoint = {200, 200}; |
| static constexpr Segment s = {eventPoint, endPoint}; |
| q.insert(Event{eventPoint, Upper{s}}); |
| EventQueue eq{std::move(q)}; |
| |
| REPORTER_ASSERT(reporter, eq.hasMoreEvents()); |
| |
| |
| TestEventHandler eh1{reporter, eventPoint, {}, {s}, {}, kHasNoDeletions}; |
| eq.handleNextEventPoint(&eh1); |
| |
| TestEventHandler eh2{reporter, endPoint, {}, {}, {}}; |
| eq.handleNextEventPoint(&eh2); |
| |
| REPORTER_ASSERT(reporter, !eq.hasMoreEvents()); |
| } |
| |
| { // Check an entire crossing event. |
| EventQueue::Queue q; |
| static constexpr Point b0 = {100, 100}; |
| static constexpr Point e0 = {200, 200}; |
| static constexpr Segment s0 = {b0, e0}; |
| static constexpr Point b1 = {200, 100}; |
| static constexpr Point e1 = {100, 200}; |
| static constexpr Segment s1 = {b1, e1}; |
| static constexpr Point crossingPoint = {150, 150}; |
| |
| // Load crossing segments into the queue |
| q.insert(Event{b0, Upper{s0}}); |
| q.insert(Event{b1, Upper{s1}}); |
| EventQueue eq{std::move(q)}; |
| |
| REPORTER_ASSERT(reporter, eq.hasMoreEvents()); |
| |
| TestEventHandler eh1{reporter, b0, {}, {s0}, {}, kHasNoDeletions}; |
| eq.handleNextEventPoint(&eh1); |
| |
| TestEventHandler eh2{reporter, b1, {}, {s1}, {{s0, s1, crossingPoint}}, kHasNoDeletions}; |
| eq.handleNextEventPoint(&eh2); |
| |
| TestEventHandler eh3{reporter, crossingPoint, {s0, s1}, {s0, s1}, {}}; |
| eq.handleNextEventPoint(&eh3); |
| |
| TestEventHandler eh4{reporter, e1, {}, {}, {}}; |
| eq.handleNextEventPoint(&eh4); |
| |
| TestEventHandler eh5{reporter, e0, {}, {}, {}}; |
| eq.handleNextEventPoint(&eh5); |
| |
| REPORTER_ASSERT(reporter, !eq.hasMoreEvents()); |
| } |
| } |
| |