Plumb through the existing test based on known sample data for the Separate Rect algorithm via Android's NATIVE_TEST harness. Signed-off-by: Rhys Kidd <rhyskidd@gmail.com> Signed-off-by: Robert Foss <robert.foss@collabora.com>
317 lines
12 KiB
C++
317 lines
12 KiB
C++
/*
|
|
* 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 "separate_rects.h"
|
|
#include <algorithm>
|
|
#include <assert.h>
|
|
#include <iostream>
|
|
#include <map>
|
|
#include <set>
|
|
#include <utility>
|
|
#include <vector>
|
|
|
|
namespace separate_rects {
|
|
|
|
enum EventType { START, END };
|
|
|
|
template <typename TId, typename TNum>
|
|
struct StartedRect {
|
|
IdSet<TId> 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<TId, TNum> &rhs) const {
|
|
return (top < rhs.top || (top == rhs.top && bottom < rhs.bottom)) ||
|
|
(top == rhs.top && bottom == rhs.bottom && id_set < rhs.id_set);
|
|
}
|
|
};
|
|
|
|
template <typename TId, typename TNum>
|
|
struct SweepEvent {
|
|
EventType type;
|
|
union {
|
|
TNum x;
|
|
TNum y;
|
|
};
|
|
|
|
TId rect_id;
|
|
|
|
bool operator<(const SweepEvent<TId, TNum> &rhs) const {
|
|
return (y < rhs.y || (y == rhs.y && rect_id < rhs.rect_id));
|
|
}
|
|
};
|
|
|
|
template <typename TNum>
|
|
std::ostream &operator<<(std::ostream &os, const Rect<TNum> &rect) {
|
|
return os << rect.bounds[0] << ", " << rect.bounds[1] << ", "
|
|
<< rect.bounds[2] << ", " << rect.bounds[3];
|
|
}
|
|
|
|
template <typename TUInt>
|
|
std::ostream &operator<<(std::ostream &os, const IdSet<TUInt> &obj) {
|
|
int bits = IdSet<TUInt>::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 <typename TNum, typename TId>
|
|
void separate_rects(const std::vector<Rect<TNum>> &in,
|
|
std::vector<RectSet<TId, TNum>> *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<TId>::max_elements) {
|
|
return;
|
|
}
|
|
|
|
// Events are when the sweep line encounters the starting or ending edge of
|
|
// any input rectangle.
|
|
std::set<SweepEvent<TId, TNum>> sweep_h_events; // Left or right bounds
|
|
std::set<SweepEvent<TId, TNum>> 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<StartedRect<TId, TNum>, 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<std::pair<TNum, IdSet<TId>>> 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<TNum> &rect = in[i];
|
|
|
|
// Filter out empty or invalid rects.
|
|
if (rect.left >= rect.right || rect.top >= rect.bottom)
|
|
continue;
|
|
|
|
SweepEvent<TId, TNum> 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<SweepEvent<TId, TNum>>::iterator it =
|
|
sweep_h_events.begin();
|
|
it != sweep_h_events.end(); ++it) {
|
|
const SweepEvent<TId, TNum> &h_evt = *it;
|
|
const Rect<TNum> &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<TId, TNum> 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<SweepEvent<TId, TNum>>::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<SweepEvent<TId, TNum>>::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<SweepEvent<TId, TNum>>::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<TId> active_set;
|
|
for (typename std::set<SweepEvent<TId, TNum>>::iterator it =
|
|
sweep_v_events.begin();
|
|
it != sweep_v_events.end(); ++it) {
|
|
const SweepEvent<TId, TNum> &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<std::pair<TNum, IdSet>>::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<StartedRect<TId, TNum>, 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<std::pair<TNum, IdSet<TId>>>::iterator it =
|
|
active_regions.begin();
|
|
it != active_regions.end(); ++it) {
|
|
IdSet<TId> 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<std::pair<TNum, IdSet<TId>>>::iterator next_it = it;
|
|
++next_it;
|
|
assert(next_it != active_regions.end());
|
|
|
|
TNum region_top = it->first;
|
|
TNum region_bottom = next_it->first;
|
|
|
|
StartedRect<TId, TNum> 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<StartedRect<TId, TNum>, 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<StartedRect<TId, TNum>, bool>::iterator it =
|
|
started_rects.begin();
|
|
it != started_rects.end();
|
|
/* inc in body */) {
|
|
if (!it->second) {
|
|
const StartedRect<TId, TNum> &proto_rect = it->first;
|
|
Rect<TNum> 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<TId, TNum>(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 separate_frects_64(const std::vector<Rect<float>> &in,
|
|
std::vector<RectSet<uint64_t, float>> *out) {
|
|
separate_rects(in, out);
|
|
}
|
|
|
|
void separate_rects_64(const std::vector<Rect<int>> &in,
|
|
std::vector<RectSet<uint64_t, int>> *out) {
|
|
separate_rects(in, out);
|
|
}
|
|
|
|
} // namespace separate_rects
|