/* * Copyright (C) 2015 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "seperate_rects.h" #include #include #include #include #include #include #include namespace seperate_rects { enum EventType { START, END }; template struct StartedRect { IdSet id_set; TNum left, top, bottom; // Note that this->left is not part of the key. That field is only to mark the // left edge of the rectangle. bool operator<(const StartedRect &rhs) const { return (top < rhs.top || (top == rhs.top && bottom < rhs.bottom)) || (top == rhs.top && bottom == rhs.bottom && id_set < rhs.id_set); } }; template struct SweepEvent { EventType type; union { TNum x; TNum y; }; TId rect_id; bool operator<(const SweepEvent &rhs) const { return (y < rhs.y || (y == rhs.y && rect_id < rhs.rect_id)); } }; template std::ostream &operator<<(std::ostream &os, const Rect &rect) { return os << rect.bounds[0] << ", " << rect.bounds[1] << ", " << rect.bounds[2] << ", " << rect.bounds[3]; } template std::ostream &operator<<(std::ostream &os, const IdSet &obj) { int bits = IdSet::max_elements; TUInt mask = ((TUInt)0x1) << (bits - 1); for (int i = 0; i < bits; i++) os << ((obj.getBits() & (mask >> i)) ? "1" : "0"); return os; } template void seperate_rects(const std::vector > &in, std::vector > *out) { // Overview: // This algorithm is a line sweep algorithm that travels from left to right. // The sweep stops at each vertical edge of each input rectangle in sorted // order of x-coordinate. At each stop, the sweep line is examined in order of // y-coordinate from top to bottom. Along the way, a running set of rectangle // IDs is either added to or subtracted from as the top and bottom edges are // encountered, respectively. At each change of that running set, a copy of // that set is recorded in along with the the y-coordinate it happened at in a // list. This list is then interpreted as a sort of vertical cross section of // our output set of non-overlapping rectangles. Based of the algorithm found // at: http://stackoverflow.com/a/2755498 if (in.size() > IdSet::max_elements) { return; } // Events are when the sweep line encounters the starting or ending edge of // any input rectangle. std::set > sweep_h_events; // Left or right bounds std::set > sweep_v_events; // Top or bottom bounds // A started rect is a rectangle whose left, top, bottom edge, and set of // rectangle IDs is known. The key of this map includes all that information // (except the left edge is never used to determine key equivalence or // ordering), std::map, bool> started_rects; // This is cleared after every event. Its declaration is here to avoid // reallocating a vector and its buffers every event. std::vector > > active_regions; // This pass will add rectangle start and end events to be triggered as the // algorithm sweeps from left to right. for (TId i = 0; i < in.size(); i++) { const Rect &rect = in[i]; SweepEvent evt; evt.rect_id = i; evt.type = START; evt.x = rect.left; sweep_h_events.insert(evt); evt.type = END; evt.x = rect.right; sweep_h_events.insert(evt); } for (typename std::set >::iterator it = sweep_h_events.begin(); it != sweep_h_events.end(); ++it) { const SweepEvent &h_evt = *it; const Rect &rect = in[h_evt.rect_id]; // During this event, we have encountered a vertical starting or ending edge // of a rectangle so want to append or remove (respectively) that rectangles // top and bottom from the vertical sweep line. SweepEvent v_evt; v_evt.rect_id = h_evt.rect_id; if (h_evt.type == START) { v_evt.type = START; v_evt.y = rect.top; sweep_v_events.insert(v_evt); v_evt.type = END; v_evt.y = rect.bottom; sweep_v_events.insert(v_evt); } else { v_evt.type = START; v_evt.y = rect.top; typename std::set >::iterator start_it = sweep_v_events.find(v_evt); assert(start_it != sweep_v_events.end()); sweep_v_events.erase(start_it); v_evt.type = END; v_evt.y = rect.bottom; typename std::set >::iterator end_it = sweep_v_events.find(v_evt); assert(end_it != sweep_v_events.end()); sweep_v_events.erase(end_it); } // Peeks ahead to see if there are other rectangles sharing a vertical edge // with the current sweep line. If so, we want to continue marking up the // sweep line before actually processing the rectangles the sweep line is // intersecting. typename std::set >::iterator next_it = it; ++next_it; if (next_it != sweep_h_events.end()) { if (next_it->x == h_evt.x) { continue; } } #ifdef RECTS_DEBUG std::cout << h_evt.x << std::endl; #endif // After the following for loop, active_regions will be a list of // y-coordinates paired with the set of rectangle IDs that are intersect at // that y-coordinate (and the current sweep line's x-coordinate). For // example if the current sweep line were the left edge of a scene with only // one rectangle of ID 0 and bounds (left, top, right, bottom) == (2, 3, 4, // 5), active_regions will be [({ 0 }, 3), {}, 5]. active_regions.clear(); IdSet active_set; for (typename std::set >::iterator it = sweep_v_events.begin(); it != sweep_v_events.end(); ++it) { const SweepEvent &v_evt = *it; if (v_evt.type == START) { active_set.add(v_evt.rect_id); } else { active_set.subtract(v_evt.rect_id); } if (active_regions.size() > 0 && active_regions.back().first == v_evt.y) { active_regions.back().second = active_set; } else { active_regions.push_back(std::make_pair(v_evt.y, active_set)); } } #ifdef RECTS_DEBUG std::cout << "x:" << h_evt.x; for (std::vector >::iterator it = active_regions.begin(); it != active_regions.end(); ++it) { std::cout << " " << it->first << "(" << it->second << ")" << ","; } std::cout << std::endl; #endif // To determine which started rectangles are ending this event, we make them // all as false, or unseen during this sweep line. for (typename std::map, bool>::iterator it = started_rects.begin(); it != started_rects.end(); ++it) { it->second = false; } // This for loop will iterate all potential new rectangles and either // discover it was already started (and then mark it true), or that it is a // new rectangle and add it to the started rectangles. A started rectangle // is unique if it has a distinct top, bottom, and set of rectangle IDs. // This is tricky because a potential rectangle could be encountered here // that has a non-unique top and bottom, so it shares geometry with an // already started rectangle, but the set of rectangle IDs differs. In that // case, we have a new rectangle, and the already existing started rectangle // will not be marked as seen ("true" in the std::pair) and will get ended // by the for loop after this one. This is as intended. for (typename std::vector > >::iterator it = active_regions.begin(); it != active_regions.end(); ++it) { IdSet region_set = it->second; if (region_set.isEmpty()) continue; // An important property of active_regions is that each region where a set // of rectangles applies is bounded at the bottom by the next (in the // vector) region's starting y-coordinate. typename std::vector > >::iterator next_it = it; ++next_it; assert(next_it != active_regions.end()); TNum region_top = it->first; TNum region_bottom = next_it->first; StartedRect rect_key; rect_key.id_set = region_set; rect_key.left = h_evt.x; rect_key.top = region_top; rect_key.bottom = region_bottom; // Remember that rect_key.left is ignored for the purposes of searching // the started rects. This follows from the fact that a previously started // rectangle would by definition have a left bound less than the current // event's x-coordinate. We are interested in continuing the started // rectangles by marking them seen (true) but we don't know, care, or wish // to change the left bound at this point. If there are no matching // rectangles for this region, start a new one and mark it as seen (true). typename std::map, bool>::iterator started_rect_it = started_rects.find(rect_key); if (started_rect_it == started_rects.end()) { started_rects[rect_key] = true; } else { started_rect_it->second = true; } } // This for loop ends all rectangles that were unseen during this event. // Because this is the first event where we didn't see this rectangle, it's // right edge is exactly the current event's x-coordinate. With this, we // have the final piece of information to output this rectangle's geometry // and set of input rectangle IDs. To end a started rectangle, we erase it // from the started_rects map and append the completed rectangle to the // output vector. for (typename std::map, bool>::iterator it = started_rects.begin(); it != started_rects.end(); /* inc in body */) { if (!it->second) { const StartedRect &proto_rect = it->first; Rect out_rect; out_rect.left = proto_rect.left; out_rect.top = proto_rect.top; out_rect.right = h_evt.x; out_rect.bottom = proto_rect.bottom; out->push_back(RectSet(proto_rect.id_set, out_rect)); started_rects.erase(it++); // Also increments out iterator. #ifdef RECTS_DEBUG std::cout << " <" << proto_rect.id_set << "(" << rect << ")" << std::endl; #endif } else { // Remember this for loop has no built in increment step. We do it here. ++it; } } } } void seperate_frects_64(const std::vector > &in, std::vector > *out) { seperate_rects(in, out); } } // namespace seperate_rects #ifdef RECTS_TEST using namespace seperate_rects; int main(int argc, char **argv) { #define RectSet RectSet #define Rect Rect #define IdSet IdSet typedef uint64_t TId; typedef float TNum; std::vector in; std::vector out; std::vector expected_out; in.push_back({0, 0, 4, 5}); in.push_back({2, 0, 6, 6}); in.push_back({4, 0, 8, 5}); in.push_back({0, 7, 8, 9}); in.push_back({10, 0, 18, 5}); in.push_back({12, 0, 16, 5}); in.push_back({20, 11, 24, 17}); in.push_back({22, 13, 26, 21}); in.push_back({32, 33, 36, 37}); in.push_back({30, 31, 38, 39}); in.push_back({40, 43, 48, 45}); in.push_back({44, 41, 46, 47}); in.push_back({50, 51, 52, 53}); in.push_back({50, 51, 52, 53}); in.push_back({50, 51, 52, 53}); for (int i = 0; i < 100000; i++) { out.clear(); seperate_rects(in, &out); } for (int i = 0; i < out.size(); i++) { std::cout << out[i].id_set << "(" << out[i].rect << ")" << std::endl; } std::cout << "# of rects: " << out.size() << std::endl; expected_out.push_back(RectSet(IdSet(0), Rect(0, 0, 2, 5))); expected_out.push_back(RectSet(IdSet(1), Rect(2, 5, 6, 6))); expected_out.push_back(RectSet(IdSet(1) | 0, Rect(2, 0, 4, 5))); expected_out.push_back(RectSet(IdSet(1) | 2, Rect(4, 0, 6, 5))); expected_out.push_back(RectSet(IdSet(2), Rect(6, 0, 8, 5))); expected_out.push_back(RectSet(IdSet(3), Rect(0, 7, 8, 9))); expected_out.push_back(RectSet(IdSet(4), Rect(10, 0, 12, 5))); expected_out.push_back(RectSet(IdSet(5) | 4, Rect(12, 0, 16, 5))); expected_out.push_back(RectSet(IdSet(4), Rect(16, 0, 18, 5))); expected_out.push_back(RectSet(IdSet(6), Rect(20, 11, 22, 17))); expected_out.push_back(RectSet(IdSet(6) | 7, Rect(22, 13, 24, 17))); expected_out.push_back(RectSet(IdSet(6), Rect(22, 11, 24, 13))); expected_out.push_back(RectSet(IdSet(7), Rect(22, 17, 24, 21))); expected_out.push_back(RectSet(IdSet(7), Rect(24, 13, 26, 21))); expected_out.push_back(RectSet(IdSet(9), Rect(30, 31, 32, 39))); expected_out.push_back(RectSet(IdSet(8) | 9, Rect(32, 33, 36, 37))); expected_out.push_back(RectSet(IdSet(9), Rect(32, 37, 36, 39))); expected_out.push_back(RectSet(IdSet(9), Rect(32, 31, 36, 33))); expected_out.push_back(RectSet(IdSet(9), Rect(36, 31, 38, 39))); expected_out.push_back(RectSet(IdSet(10), Rect(40, 43, 44, 45))); expected_out.push_back(RectSet(IdSet(10) | 11, Rect(44, 43, 46, 45))); expected_out.push_back(RectSet(IdSet(11), Rect(44, 41, 46, 43))); expected_out.push_back(RectSet(IdSet(11), Rect(44, 45, 46, 47))); expected_out.push_back(RectSet(IdSet(10), Rect(46, 43, 48, 45))); expected_out.push_back(RectSet(IdSet(12) | 13 | 14, Rect(50, 51, 52, 53))); for (int i = 0; i < expected_out.size(); i++) { RectSet &ex_out = expected_out[i]; if (std::find(out.begin(), out.end(), ex_out) == out.end()) { std::cout << "Missing Rect: " << ex_out.id_set << "(" << ex_out.rect << ")" << std::endl; } } for (int i = 0; i < out.size(); i++) { RectSet &actual_out = out[i]; if (std::find(expected_out.begin(), expected_out.end(), actual_out) == expected_out.end()) { std::cout << "Extra Rect: " << actual_out.id_set << "(" << actual_out.rect << ")" << std::endl; } } return 0; } #endif