Detect if two rectangles can be combined into a single rectangle - algorithm

I'm looking for an algorithm that takes two rectangles defined by (xa1,ya1,xa2,ya2) and (xb1,yb1,xb2,yb2), checks if they can be combined into a single rectangle and if they can, returns the new rectangle. An example:
xa1=0,ya1=0,xa2=320,ya2=119
xb1=0,yb1=120,xb2=320,yb2=239
These two rectangles can be combined into the following rectangle:
xc1=0,yc1=0,xc2=320,yc2=240
How would you implement such an algorithm? Thanks!

I'd draw the following pictures and would write it down as algorithm:
...xxxxxxx xxxxxxx....
. x . x x . x .
. x . x x . x .
...xxxxxxx xxxxxxx....
xxxxxxx .......
x x . .
x.....x xxxxxxx
xxxxxxx x.....x
. . x x
....... xxxxxxx
..........
. .
. xxxx .
. x x .
. x x .
. xxxx .
..........
xxxxxxxxxxxxxx
x x
x ....... x
x . . x
x . . x
x ....... x
x x
xxxxxxxxxxxxxx
Check out for corner cases!

After much fiddling I kind of worked out what you want. Note that there is still some contention to what you mean by 'strict bounding box': the sample in you original question does not satisfy the description you gave:
But the rectangles should only be combined if the bounding box is exactly the size of the two rectangles combined, i.e. the area of the bounding rectangle must be exactly the same as the size of the areas of the two source rectangles. If the area of rect 1 is a1, and the area of rect2 is a2, and the area of the bounding rect is a3, then a1+a2=a3.
This implementation should give you plenty of ideas, and I'm sure you know how to write
r.area() == a.area() + b.area()
if you really wanted that.
Codepad code:
// Final proposal: combine adjacent rectangles,
// if they are 'flush': almost touching
#include <iostream>
struct R
{
int x1,y1,x2,y2;
int height() const { return y2-y1; }
int width() const { return y2-y1; }
void normalize()
{
if (x1>x2) std::swap(x1,x2);
if (y1>y2) std::swap(y1,y2);
}
/*
* adjacent: return whether two rectangles
* are adjacent; the tolerance in pixels
* allow you to specify the gap:
* tolerance = 0: require at least one pixel overlap
* tolerance = 1: accepts 'flush' adjacent neighbours
* Negative tolerance require more overlap;
* tolerance > 1 allows gaps between rects;
*/
bool adjacent(R const& other, int tolerance=1) const
{
return !( (other.x1 - x2) > tolerance
|| (x1 - other.x2) > tolerance
|| (other.y1 - y2) > tolerance
|| (y1 - other.y2) > tolerance);
}
};
/*
* tolerance: see R::adjacent()
*
* strict: only allow strict ('pure') combinations of rects
*/
R combined(R const& a, R const& b, int tolerance=1, bool strict=false)
{
if (!a.adjacent(b, tolerance))
throw "combined(a,b): a and b don't satisfy adjacency requirements (are the coords normalized?)";
R r = { min(a.x1, b.x1), 1,1,1};
r.x1 = min(a.x1, b.x1);
r.y1 = min(a.y1, b.y1);
r.x2 = max(a.x2, b.x2);
r.y2 = max(a.y2, b.y2);
if (!strict)
return r;
if ( (r.height() <= max(a.height(), b.height()))
&& (r.width () <= max(a.width (), b.width ())) )
return r;
else
throw "combined(a,b): strict combination not available";
}
std::ostream& operator<<(std::ostream &os, R const& r)
{
return os << '(' << r.x1 << "," << r.y1 << ")-(" << r.x2 << ',' << r.y2 << ')';
}
int main()
{
const int tolerance = 1;
{
std::cout << "sample from original question" << std::endl;
R a = { 0, 0, 320, 119 }; /* a.normalize(); */
R b = { 0, 120, 320, 239 }; /* b.normalize(); */
std::cout << "a: " << a << "\t b: " << b << std::endl;
std::cout << "r: " << combined(a,b, tolerance) << std::endl;
}
{
std::cout << "sample from the comment" << std::endl;
R a = { 0, 0, 1, 320 }; /* a.normalize(); */
R b = { 0, 0, 2, 320 }; /* b.normalize(); */
std::cout << "a: " << a << "\t b: " << b << std::endl;
// NOTE: strict mode
std::cout << "r: " << combined(a,b, tolerance, true) << std::endl;
}
}
Output:
sample from original question
a: (0,0)-(320,119) b: (0,120)-(320,239)
r: (0,0)-(320,239)
sample from the comment
a: (0,0)-(1,320) b: (0,0)-(2,320)
r: (0,0)-(2,320)

They can be combined if and only if one pair of opposite sides of one rectangle overlaps one pair of opposite sides of the other rectangle. By overlap, I mean if they are parallel and contain at least one point in common.
You should be able to figure out the code ;)
EDIT: Oh I forgot to mention the case where the two rectangles are completely overlapping. That shouldn't be too hard to check either.

The two rectangles must intersect. The corners of the bounding rectangle must all land on existing corners.
Those two conditions are necessary and sufficient. Obviously the rectangles have to intersect, and, because you can't create a non-corner empty area using only 2 intersecting rectangles, the bounding corners must land on existing corners.
return r1.Intersects(r2) and r1.BoundingRectangleWith(r2).Corners.IsSubsetOf(r1.Corners.Union(r2.Corners))
Implementing Intersects, BoundingRectangleWith, Corners, and IsSubsetOf is straightforward. You can then inline them for better performance, but it will be a mess of unreadable comparisons.
Edit
One of your comments suggests you don't want the rectangles to overlap, only to touch. In that case you only need to check that on one axis (i.e. X or Y) the rectangle's ranges are equal and on the other axis the ranges touch. Two ranges touch if the median of their bounds has 2 occurrences. Note that if you want right=1 to touch left=2, you need to add 1 to the ceiling bounds.

Related

Performance issue with a n X n search in a container

I have a customized array implementation for a geometry (vertex) . Each element of the array is represented by the vertex which has a Point . Now I want to check the distance between each point for the vertex in the array . So essentially for every vertex in the array of size n I will loop till n and calculate the distance of vertex point with all n vertex points in the array . So a pseudo code will look like this
func MyFunc( Array iVrtxList , vrtx inpVertex )
{
point refPt = inpVertex->getPoint();
for ( i=0 ; i < iVrtxList.size(); i++)
{
if( distanceBetween(iVertexList(i).point ,rePt ) == 0 )
return
}
iVrtxList.add(inpVertex);
}
}
So I want to avoid N X N looping . I thought of sorting the container and then check only the subsequent element for the distance . However I seem to miss some elements
I kind of achieved our objective to skip the duplicate vertices without using a N X N approach . I used a multimap to keep track of the vertices . The key was hashed with the x,y,z values which we adjusted to precision to make it work with the dataset . However the hash calculation is vulnerable as any mapping causing collision will defeat the purpose . Following are the definitions
class PointCoords
{
public:
PointCoords(double ix, double iy, double iz) : _x(ix), _y(iy), _z(iz) { };
double _x;
double _y;
double _z;
};
class PointCoords_hash
{
public:
size_t operator()(const PointCoords& v) const
{
auto f1 = std::hash<double>{}(round(v._x * 10) / 10);
auto f2 = std::hash<double>{}(round(v._y * 10) / 10);
auto f3 = std::hash<double>{}(round(v._z * 10) / 10);
size_t hCode = (f1 ^ f2 ^ f3) << 1;
return ((f1 ^ f2 ^ f3) << 1);
};
};
class PointCoords_equal
{
public:
bool operator()(const PointCoords& u, const PointCoords& v) const
{
return (equal(u._x, v._x, 1e-6) &&
equal(u._y, v._y, 1e-6) &&
equal(u._z, v._z, 1e-6));
};
};
bool :equal( double d1, double d2, double err)
{
return ( fabs( d1 -d2) <= err);
}

Properly generate convex hull and get plane equations

I am completely new to Computational Geometry. I want to generate convex hull of a set of points and then get plane equations for the generated convex polyhedron so that I can check inclusion/exclusion of points. I have followed the docs and tried the whole procedure probably a dozen times but there is always some issue. Maybe I'm missing some subtle point here. The whole procedure is as follows. I have the following plot, generated in Mathematica.
I want to include every point that is on the plot inside a convex hull. So I take all the points lying on all corners of both planes and the origin (Maybe that's the problem. Maybe there is a way to properly choose points so that all points on the plot are covered). The points for this specific plot are as follows. Note that the points are generated using infinite precision so they are exact values.
pts = {
{-24298771/25000000000,-223461425901/50000000000,0},
{11285077/10000000000,-223461425901/50000000000,0},
{-24298771/25000000000,0,0},
{-24298771/25000000000,-11285077/10000000000,0},
{-24298771/25000000000,120551411529/25000000000,-24298771/25000000000},
{11285077/10000000000,120551411529/25000000000,11285077/10000000000},
{11285077/10000000000,0,11285077/10000000000},
{-24298771/25000000000,24298771/25000000000,-24298771/25000000000},
{0,0,0}
};
Then, I use the following CGAL program to generate the convex hull and plane equations. Again, trying to keep things in infinite precision.
#include <CGAL/Exact_predicates_exact_constructions_kernel.h>
#include <CGAL/GMP/Gmpq_type.h>
#include <CGAL/Polyhedron_3.h>
#include <CGAL/convex_hull_3.h>
#include <CGAL/Side_of_triangle_mesh.h>
#include <CGAL/number_utils.h>
#include <unistd.h>
#include <iomanip>
typedef CGAL::Exact_predicates_exact_constructions_kernel Kernel;
typedef CGAL::Polyhedron_3<Kernel> Polyhedron_3;
typedef Kernel::Point_3 Point_3;
typedef Kernel::Plane_3 Plane_3;
typedef Kernel::Vector_3 Vector_3;
typedef CGAL::Side_of_triangle_mesh<Polyhedron_3, Kernel> Point_inside;
struct Plane_equation {
template <class Facet>
typename Facet::Plane_3 operator()( Facet& f) {
typename Facet::Halfedge_handle h = f.halfedge();
typedef typename Facet::Plane_3 Plane;
return Plane( h->vertex()->point(),
h->next()->vertex()->point(),
h->next()->next()->vertex()->point());
}
};
Point_3 create_point(std::vector<std::string> points) {
auto x = points[0], y = points[1], z = points[2];
Point_3 p;
std::istringstream input(x + " " + y + " " + z);
input >> p;
return p;
}
std::vector<std::string> create_coords_from_line(std::string line) {
std::vector<std::string> points;
std::istringstream stream(line);
std::string pt;
getline(stream, pt, ' ');
points.push_back(pt);
getline(stream, pt, ' ');
points.push_back(pt);
getline(stream, pt);
points.push_back(pt);
return points;
}
int main() {
std::vector<Point_3> points;
std::string line;
for (auto i = 0; i < 9; ++i) {
getline(std::cin, line);
points.push_back(create_point(create_coords_from_line(line)));
}
Polyhedron_3 poly;
CGAL::convex_hull_3(points.begin(), points.end(), poly);
// CGAL::draw(poly);
std::transform(poly.facets_begin(), poly.facets_end(), poly.planes_begin(), Plane_equation());
CGAL::set_pretty_mode(std::cout);
for (auto it = poly.planes_begin(); it != poly.planes_end(); ++it) {
if (isatty(fileno(stdin))) {
std::cout << "A = " << it->a().exact() << "\n";
std::cout << "B = " << it->b().exact() << "\n";
std::cout << "C = " << it->c().exact() << "\n";
std::cout << "D = " << it->d().exact() << "\n";
std::cout << "\n";
} else {
std::cout << it->a().exact() << " " << it->b().exact() << " "
<< it->c().exact() << " " << it->d().exact() << "\n";
}
}
return EXIT_SUCCESS;
}
Now, in order to make sure that the generated equations are correct and cover all the points, I create a Z3py script. In that, f is the function used to generate the plot, g is the conjunction of all plane equations with proper inequality (<, =, >). Then I check if f ---> g. I use the theory of reals for infinite precision. But it always comes up with a counter-example. And these counter examples are always on some edge of the plane. Here are a couple of pictures where red circle indicates the location of counterexample. This is a different plot from the one above but the process is the same. Just the input values to f is different.
Now, I don't really need infinite precision for my problem. But I would like to make sure that the procedure works with infinite precision so that I can be confident about correctness. But then I tried with CPLEX, which uses only 64-bits, and with that too, counterexamples were generated, in similar fashion to Z3. Here's an example
Now I have no idea in which step of the process am I making a mistake. My suspicion is the selection of initial points for convex hull. It would be great if someone can help me find the convex hull properly. As far as I have read, the convex hull algorithms are exact if infinite precision is used. That's why I didn't use Mathematica's convex hull feature, because it is not using infinite precision.
EDIT: There are two smaller planes which Mathematica is failing to show, as shown below. I want all points on those planes to be selected too. But the endpoints of the smaller planes coincide with the endpoints of larger planes. That's why I have taken only the corners of larger planes.
EDIT 2: Due to the range of y being so large compared to the other two variables, the convex hull generated for the above specified points looks just like a single line.
However, after diving the y values by 1000, we can see a clearer picture.

Eigen Library Euler Order Sequencing

I'm trying to understand the Eigen library's "eulerAngles" function, and I have some test code that doesn't make sense.
My testing includes determining if the Euler function call "eulerAngles(0, 1, 2)" corresponds to a "XYZ" Euler sequence of the static frame. For Eigen's column major matrix multiplication, it should correspond to "Z * Y * X". I have confirmed that with some simple tests, but for negative angles around the X axis I don't understand the results. Instead of using a Euler 3 Angle constructor, I'm using the "AngleAxisd" function:
rot3x3 = AngleAxisd( -M_PI, Vector3d::UnitX() );
Vector3d vec = rot3x3.eulerAngles(0, 1, 2);
For positive angles around the X axis, it works the way I expect. For the -M_PI angle (equivalent to 180 degrees), I'm seeing the following:
Euler angles: -0, 3.14159, -3.14159
I expect the first element in the array to be near -π, and the other two to be near zero. I expect the range of the first and last angles to be ±π, and the middle angle to be ±π/2. The middle angle is out of range.
The program is below:
#include "stdafx.h"
#include <iostream>
#include "<Eigen\Dense"
#include "<unsupported\Eigen\MatrixFunctions"
using namespace std;
using namespace Eigen;
int main(int argc, char* argv[])
{
Matrix3d rot3x3;
rot3x3 = AngleAxisd( -M_PI, Vector3d::UnitX() );
cout << "Here is rot3x3:" << endl << rot3x3 << endl << endl;
Vector3d vec = rot3x3.eulerAngles(0, 1, 2); // => 1-2-3 => XYZ => Z*Y*X
cout << "Euler angles: " << vec.x() << ", "
<< vec.y() << ", "
<< vec.z() << endl << endl;
system("pause");
return 0;
}
The output is:
Here is rot3x3:
1 0 0
0 -1 1.22465e-016
0 -1.22465e-016 -1
Euler angles: -0, 3.14159, -3.14159
This result is equivalent to a -π around the X axis (and it is the same as a positive π rotation around the X axis as well). Shouldn't the middle angle though be in the range that is equivalent to ±90 degrees?
Euler angles are not uniquely defined. In case of ambiguity, eulerAngles always pick the solution that minimizes the first angles. See also the following bug entries for related discussions: 609, 801, 947.

Rotated rectangle rasterisation algorithm

In a nutshell: I want to do a non-approximate version of Bresenham's line algorithm, but for a rectangle rather than a line, and whose points aren't necessarily aligned to the grid.
Given a square grid, and a rectangle comprising four non-grid-aligned points, I want to find a list of all grid squares that are covered, partially or completely, by the rectangle.
Bresenham's line algorithm is approximate – not all partially covered squares are identified. I'm looking for a "perfect" algorithm, that has no false positives or negatives.
It's an old question, but I have solved this issue (C++)
https://github.com/feelinfine/tracer
Maybe it will be usefull for someone
(sorry for my poor english)
Single line tracing
template <typename PointType>
std::set<V2i> trace_line(const PointType& _start_point, const PointType& _end_point, size_t _cell_size)
{
auto point_to_grid_fnc = [_cell_size](const auto& _point)
{
return V2i(std::floor((double)_point.x / _cell_size), std::floor((double)_point.y / _cell_size));
};
V2i start_cell = point_to_grid_fnc(_start_point);
V2i last_cell = point_to_grid_fnc(_end_point);
PointType direction = _end_point - _start_point;
//Moving direction (cells)
int step_x = (direction.x >= 0) ? 1 : -1;
int step_y = (direction.y >= 0) ? 1 : -1;
//Normalize vector
double hypot = std::hypot(direction.x, direction.y);
V2d norm_direction(direction.x / hypot, direction.y / hypot);
//Distance to the nearest square side
double near_x = (step_x >= 0) ? (start_cell.x + 1)*_cell_size - _start_point.x : _start_point.x - (start_cell.x*_cell_size);
double near_y = (step_y >= 0) ? (start_cell.y + 1)*_cell_size - _start_point.y : _start_point.y - (start_cell.y*_cell_size);
//How far along the ray we must move to cross the first vertical (ray_step_to_vside) / or horizontal (ray_step_to_hside) grid line
double ray_step_to_vside = (norm_direction.x != 0) ? near_x / norm_direction.x : std::numeric_limits<double>::max();
double ray_step_to_hside = (norm_direction.y != 0) ? near_y / norm_direction.y : std::numeric_limits<double>::max();
//How far along the ray we must move for horizontal (dx)/ or vertical (dy) component of such movement to equal the cell size
double dx = (norm_direction.x != 0) ? _cell_size / norm_direction.x : std::numeric_limits<double>::max();
double dy = (norm_direction.y != 0) ? _cell_size / norm_direction.y : std::numeric_limits<double>::max();
//Tracing loop
std::set<V2i> cells;
cells.insert(start_cell);
V2i current_cell = start_cell;
size_t grid_bound_x = std::abs(last_cell.x - start_cell.x);
size_t grid_bound_y = std::abs(last_cell.y - start_cell.y);
size_t counter = 0;
while (counter != (grid_bound_x + grid_bound_y))
{
if (std::abs(ray_step_to_vside) < std::abs(ray_step_to_hside))
{
ray_step_to_vside = ray_step_to_vside + dx; //to the next vertical grid line
current_cell.x = current_cell.x + step_x;
}
else
{
ray_step_to_hside = ray_step_to_hside + dy;//to the next horizontal grid line
current_cell.y = current_cell.y + step_y;
}
++counter;
cells.insert(current_cell);
};
return cells;
}
Get all cells
template <typename Container>
std::set<V2i> pick_cells(Container&& _points, size_t _cell_size)
{
if (_points.size() < 2 || _cell_size <= 0)
return std::set<V2i>();
Container points = std::forward<Container>(_points);
auto add_to_set = [](auto& _set, const auto& _to_append)
{
_set.insert(std::cbegin(_to_append), std::cend(_to_append));
};
//Outline
std::set<V2i> cells;
/*
for (auto it = std::begin(_points); it != std::prev(std::end(_points)); ++it)
add_to_set(cells, trace_line(*it, *std::next(it), _cell_size));
add_to_set(cells, trace_line(_points.back(), _points.front(), _cell_size));
*/
//Maybe this code works faster
std::vector<std::future<std::set<V2i> > > results;
using PointType = decltype(points.cbegin())::value_type;
for (auto it = points.cbegin(); it != std::prev(points.cend()); ++it)
results.push_back(std::async(trace_line<PointType>, *it, *std::next(it), _cell_size));
results.push_back(std::async(trace_line<PointType>, points.back(), points.front(), _cell_size));
for (auto& it : results)
add_to_set(cells, it.get());
//Inner
std::set<V2i> to_add;
int last_x = cells.begin()->x;
int counter = cells.begin()->y;
for (auto& it : cells)
{
if (last_x != it.x)
{
counter = it.y;
last_x = it.x;
}
if (it.y > counter)
{
for (int i = counter; i < it.y; ++i)
to_add.insert(V2i(it.x, i));
}
++counter;
}
add_to_set(cells, to_add);
return cells;
}
Types
template <typename _T>
struct V2
{
_T x, y;
V2(_T _x = 0, _T _y = 0) : x(_x), y(_y)
{
};
V2 operator-(const V2& _rhs) const
{
return V2(x - _rhs.x, y - _rhs.y);
}
bool operator==(const V2& _rhs) const
{
return (x == _rhs.x) && (y == _rhs.y);
}
//for std::set sorting
bool operator<(const V2& _rhs) const
{
return (x == _rhs.x) ? (y < _rhs.y) : (x < _rhs.x);
}
};
using V2d = V2<double>;
using V2i = V2<int>;
Usage
std::vector<V2d> points = { {200, 200}, {400, 400}, {500,100} };
size_t cell_size = 30;
auto cells = pick_cells(points, cell_size);
for (auto& it : cells)
... //do something with cells
You can use a scanline approach. The rectangle is a closed convex polygon, so it is sufficient to store the leftmost and rightmost pixel for each horizontal scanline. (And the top and bottom scanlines, too.)
The Bresenham algorithm tries to draw a thin, visually pleasing line without adjacent cells in the smaller dimension. We need an algorithm that visits each cell that the edges of the polygon pass through. The basic idea is to find the starting cell (x, y) for each edge and then to adjust x whenever the edge intersects a vertical border and to adjust y when it intersects a horizontal border.
We can represent the intersections by means of a normalised coordinate s that travels along the edge and that is 0.0 at the first node n1 and 1.0 at the second node n2.
var x = Math.floor(n1.x / cellsize);
var y = Math.floor(n1.y / cellsize);
var s = 0;
The vertical insersections can the be represented as equidistant steps of with dsx from an initial sx.
var dx = n2.x - n1.x;
var sx = 10; // default value > 1.0
// first intersection
if (dx < 0) sx = (cellsize * x - n1.x) / dx;
if (dx > 0) sx = (cellsize * (x + 1) - n1.x) / dx;
var dsx = (dx != 0) ? grid / Math.abs(dx) : 0;
Likewise for the horizontal intersecions. A default value greater than 1.0 catches the cases of horizontal and vertical lines. Add the first point to the scanline data:
add(scan, x, y);
Then we can visit the next adjacent cell by looking at the next intersection with the smallest s.
while (sx <= 1 || sy <= 1) {
if (sx < sy) {
sx += dsx;
if (dx > 0) x++; else x--;
} else {
sy += dsy;
if (dy > 0) y++; else y--;
}
add(scan, x, y);
}
Do this for all four edges and with the same scanline data. Then fill all cells:
for (var y in scan) {
var x = scan[y].min;
var xend = scan[y].max + 1;
while (x < xend) {
// do something with cell (x, y)
x++;
}
}
(I have only skimmed the links MBo provided. It seems that the approach presented in that paper is essentially the same as mine. If so, please excuse the redundant answer, but after working this out I thought I could as well post it.)
This is sub-optimal but might give a general idea.
First off treat the special case of the rectangle being aligned horizontally or vertically separately. This is pretty easy to test for and make the rest simpler.
You can represent the rectangle as a set of 4 inequalities a1 x + b1 y >= c1 a1 x + b1 y <= c2 a3 x + b3 y >= c3 a3 x + b3 y <= c4 as the edges of the rectangles are parallel some of the constants are the same. You also have (up to a multiple) a3=b1 and b3=-a1. You can multiply each inequality by a common factor so you are working with integers.
Now consider each scan line with a fixed value of y.
For each value of y find the four points where the lines intersect the scan line. That is find the solution with each line above. A little bit of logic will find the minimum and maximum values of x. Plot all pixels between these values.
You condition that you want all partially covered squares makes things a little trickier. You can solve this by considering two adjacent scan lines. You want to plot the points between the minimum x for both lines and the maximum for the both lines. If say
a1 x+b1 y>=c is the inequality for the bottom left line in the figure. You want the find the largest x such that a1 x + b1 y < c this will be floor((c-b1 y)/a1) call this minx(y) also find minx(y+1) and the left hand point will be the minimum of these two values.
There is many easy optimisation you can find the y-values of the top and bottom corners reducing the range of y-values to test. You should only need to test two side. For each end point of each line there is one multiplication, one subtraction and one division. The division is the slowest part I think about 4 time slower than other ops. You might be able to remove this with a Bresenham or DDA algorithms others have mentioned.
There is method of Amanatides and Woo to enumerate all intersected cells
A Fast Voxel Traversal Algorithm for Ray Tracing.
Here is practical implementation.
As side effect for you - you'll get points of intersection with grid lines - it may be useful if you need areas of partially covered cells (for antialiasing etc).

What is an Efficient algorithm to find Area of Overlapping Rectangles

My situation
Input: a set of rectangles
each rect is comprised of 4 doubles like this: (x0,y0,x1,y1)
they are not "rotated" at any angle, all they are "normal" rectangles that go "up/down" and "left/right" with respect to the screen
they are randomly placed - they may be touching at the edges, overlapping , or not have any contact
I will have several hundred rectangles
this is implemented in C#
I need to find
The area that is formed by their overlap - all the area in the canvas that more than one rectangle "covers" (for example with two rectangles, it would be the intersection)
I don't need the geometry of the overlap - just the area (example: 4 sq inches)
Overlaps shouldn't be counted multiple times - so for example imagine 3 rects that have the same size and position - they are right on top of each other - this area should be counted once (not three times)
Example
The image below contains thre rectangles: A,B,C
A and B overlap (as indicated by dashes)
B and C overlap (as indicated by dashes)
What I am looking for is the area where the dashes are shown
-
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAA--------------BBB
AAAAAAAAAAAAAAAA--------------BBB
AAAAAAAAAAAAAAAA--------------BBB
AAAAAAAAAAAAAAAA--------------BBB
BBBBBBBBBBBBBBBBB
BBBBBBBBBBBBBBBBB
BBBBBBBBBBBBBBBBB
BBBBBB-----------CCCCCCCC
BBBBBB-----------CCCCCCCC
BBBBBB-----------CCCCCCCC
CCCCCCCCCCCCCCCCCCC
CCCCCCCCCCCCCCCCCCC
CCCCCCCCCCCCCCCCCCC
CCCCCCCCCCCCCCCCCCC
An efficient way of computing this area is to use a sweep algorithm. Let us assume that we sweep a vertical line L(x) through the union of rectangles U:
first of all, you need to build an event queue Q, which is, in this case, the ordered list of all x-coordinates (left and right) of the rectangles.
during the sweep, you should maintain a 1D datastructure, which should give you the total length of the intersection of L(x) and U. The important thing is that this length is constant between two consecutive events q and q' of Q. So, if l(q) denotes the total length of L(q+) (i.e. L just on the rightside of q) intersected with U, the area swept by L between events q and q' is exactly l(q)*(q' - q).
you just have to sum up all these swept areas to get the total one.
We still have to solve the 1D problem. You want a 1D structure, which computes dynamically a union of (vertical) segments. By dynamically, I mean that you sometimes add a new segment, and sometimes remove one.
I already detailed in my answer to this collapsing ranges question how to do it in a static way (which is in fact a 1D sweep). So if you want something simple, you can directly apply that (by recomputing the union for each event). If you want something more efficient, you just need to adapt it a bit:
assuming that you know the union of segments S1...Sn consists of disjoints segments D1...Dk. Adding Sn+1 is very easy, you just have to locate both ends of Sn+1 amongs the ends of D1...Dk.
assuming that you know the union of segments S1...Sn consists of disjoints segments D1...Dk, removing segment Si (assuming that Si was included in Dj) means recomputing the union of segments that Dj consisted of, except Si (using the static algorithm).
This is your dynamic algorithm. Assuming that you will use sorted sets with log-time location queries to represent D1...Dk, this is probably the most efficient non-specialized method you can get.
One way-out approach is to plot it to a canvas! Draw each rectangle using a semi-transparent colour. The .NET runtime will be doing the drawing in optimised, native code - or even using a hardware accelerator.
Then, you have to read-back the pixels. Is each pixel the background colour, the rectangle colour, or another colour? The only way it can be another colour is if two or more rectangles overlapped...
If this is too much of a cheat, I'd recommend the quad-tree as another answerer did, or the r-tree.
The simplest solution
import numpy as np
A = np.zeros((100, 100))
B = np.zeros((100, 100))
A[rect1.top : rect1.bottom, rect1.left : rect1.right] = 1
B[rect2.top : rect2.bottom, rect2.left : rect2.right] = 1
area_of_union = np.sum((A + B) > 0)
area_of_intersect = np.sum((A + B) > 1)
In this example, we create two zero-matrices that are the size of the canvas. For each rectangle, fill one of these matrices with ones where the rectangle takes up space. Then sum the matrices. Now sum(A+B > 0) is the area of the union, and sum(A+B > 1) is the area of the overlap. This example can easily generalize to multiple rectangles.
This is some quick and dirty code that I used in the TopCoder SRM 160 Div 2.
t = top
b = botttom
l = left
r = right
public class Rect
{
public int t, b, l, r;
public Rect(int _l, int _b, int _r, int _t)
{
t = _t;
b = _b;
l = _l;
r = _r;
}
public bool Intersects(Rect R)
{
return !(l > R.r || R.l > r || R.b > t || b > R.t);
}
public Rect Intersection(Rect R)
{
if(!this.Intersects(R))
return new Rect(0,0,0,0);
int [] horiz = {l, r, R.l, R.r};
Array.Sort(horiz);
int [] vert = {b, t, R.b, R.t};
Array.Sort(vert);
return new Rect(horiz[1], vert[1], horiz[2], vert[2]);
}
public int Area()
{
return (t - b)*(r-l);
}
public override string ToString()
{
return l + " " + b + " " + r + " " + t;
}
}
Here's something that off the top of my head sounds like it might work:
Create a dictionary with a double key, and a list of rectangle+boolean values, like this:
Dictionary< Double, List< KeyValuePair< Rectangle, Boolean>>> rectangles;
For each rectangle in your set, find the corresponding list for the x0 and the x1 values, and add the rectangle to that list, with a boolean value of true for x0, and false for x1. This way you now have a complete list of all the x-coordinates that each rectangle either enters (true) or leaves (false) the x-direction
Grab all the keys from that dictionary (all the distinct x-coordinates), sort them, and loop through them in order, make sure you can get at both the current x-value, and the next one as well (you need them both). This gives you individual strips of rectangles
Maintain a set of rectangles you're currently looking at, which starts out empty. For each x-value you iterate over in point 3, if the rectangle is registered with a true value, add it to the set, otherwise remove it.
For a strip, sort the rectangles by their y-coordinate
Loop through the rectangles in the strip, counting overlapping distances (unclear to me as of yet how to do this efficiently)
Calculate width of strip times height of overlapping distances to get areas
Example, 5 rectangles, draw on top of each other, from a to e:
aaaaaaaaaaaaaaaa bbbbbbbbbbbbbbbbb
aaaaaaaaaaaaaaaa bbbbbbbbbbbbbbbbb
aaaaaaaaaaaaaaaa bbbbbbbbbbbbbbbbb
aaaaaaaaaaaaaaaa bbbbbbbbbbbbbbbbb
aaaaaaaadddddddddddddddddddddddddddddbbbbbb
aaaaaaaadddddddddddddddddddddddddddddbbbbbb
ddddddddddddddddddddddddddddd
ddddddddddddddddddddddddddddd
ddddddddddddddeeeeeeeeeeeeeeeeee
ddddddddddddddeeeeeeeeeeeeeeeeee
ddddddddddddddeeeeeeeeeeeeeeeeee
ccccccccddddddddddddddeeeeeeeeeeeeeeeeee
ccccccccddddddddddddddeeeeeeeeeeeeeeeeee
cccccccccccc eeeeeeeeeeeeeeeeee
cccccccccccc eeeeeeeeeeeeeeeeee
cccccccccccc
cccccccccccc
Here's the list of x-coordinates:
v v v v v v v v v
|aaaaaaa|aa|aaaa | bbbbbbbbbb|bb|bbb
|aaaaaaa|aa|aaaa | bbbbbbbbbb|bb|bbb
|aaaaaaa|aa|aaaa | bbbbbbbbbb|bb|bbb
|aaaaaaa|aa|aaaa | bbbbbbbbbb|bb|bbb
|aaaaaaaddd|dddddddddd|ddddddddddddddbb|bbb
|aaaaaaaddd|dddddddddd|ddddddddddddddbb|bbb
| ddd|dddddddddd|dddddddddddddd |
| ddd|dddddddddd|dddddddddddddd |
| ddd|ddddddddddeeeeeeeeeeeeeeeeee
| ddd|ddddddddddeeeeeeeeeeeeeeeeee
| ddd|ddddddddddeeeeeeeeeeeeeeeeee
ccccccccddd|ddddddddddeeeeeeeeeeeeeeeeee
ccccccccddd|ddddddddddeeeeeeeeeeeeeeeeee
cccccccccccc eeeeeeeeeeeeeeeeee
cccccccccccc eeeeeeeeeeeeeeeeee
cccccccccccc
cccccccccccc
The list would be (where each v is simply given a coordinate starting at 0 and going up):
0: +a, +c
1: +d
2: -c
3: -a
4: +e
5: +b
6: -d
7: -e
8: -b
Each strip would thus be (rectangles sorted from top to bottom):
0-1: a, c
1-2: a, d, c
2-3: a, d
3-4: d
4-5: d, e
5-6: b, d, e
6-7: b, e
7-8: b
for each strip, the overlap would be:
0-1: none
1-2: a/d, d/c
2-3: a/d
3-4: none
4-5: d/e
5-6: b/d, d/e
6-7: none
7-8: none
I'd imagine that a variation of the sort + enter/leave algorithm for the top-bottom check would be doable as well:
sort the rectangles we're currently analyzing in the strip, top to bottom, for rectangles with the same top-coordinate, sort them by bottom coordinate as well
iterate through the y-coordinates, and when you enter a rectangle, add it to the set, when you leave a rectangle, remove it from the set
whenever the set has more than one rectangle, you have overlap (and if you make sure to add/remove all rectangles that have the same top/bottom coordinate you're currently looking at, multiple overlapping rectangles would not be a problem
For the 1-2 strip above, you would iterate like this:
0. empty set, zero sum
1. enter a, add a to set (1 rectangle in set)
2. enter d, add d to set (>1 rectangles in set = overlap, store this y-coordinate)
3. leave a, remove a from set (now back from >1 rectangles in set, add to sum: y - stored_y
4. enter c, add c to set (>1 rectangles in set = overlap, store this y-coordinate)
5. leave d, remove d from set (now back from >1 rectangles in set, add to sum: y - stored_y)
6. multiply sum with width of strip to get overlapping areas
You would not actually have to maintain an actual set here either, just the count of the rectangles you're inside, whenever this goes from 1 to 2, store the y, and whenever it goes from 2 down to 1, calculate current y - stored y, and sum this difference.
Hope this was understandable, and as I said, this is off the top of my head, not tested in any way.
Using the example:
1 2 3 4 5 6
1 +---+---+
| |
2 + A +---+---+
| | B |
3 + + +---+---+
| | | | |
4 +---+---+---+---+ +
| |
5 + C +
| |
6 +---+---+
1) collect all the x coordinates (both left and right) into a list, then sort it and remove duplicates
1 3 4 5 6
2) collect all the y coordinates (both top and bottom) into a list, then sort it and remove duplicates
1 2 3 4 6
3) create a 2D array by number of gaps between the unique x coordinates * number of gaps between the unique y coordinates.
4 * 4
4) paint all the rectangles into this grid, incrementing the count of each cell it occurs over:
1 3 4 5 6
1 +---+
| 1 | 0 0 0
2 +---+---+---+
| 1 | 1 | 1 | 0
3 +---+---+---+---+
| 1 | 1 | 2 | 1 |
4 +---+---+---+---+
0 0 | 1 | 1 |
6 +---+---+
5) the sum total of the areas of the cells in the grid that have a count greater than one is the area of overlap. For better efficiency in sparse use-cases, you can actually keep a running total of the area as you paint the rectangles, each time you move a cell from 1 to 2.
In the question, the rectangles are described as being four doubles. Doubles typically contain rounding errors, and error might creep into your computed area of overlap. If the legal coordinates are at finite points, consider using an integer representation.
PS using the hardware accelerator as in my other answer is not such a shabby idea, if the resolution is acceptable. Its also easy to implement in a lot less code than the approach I outline above. Horses for courses.
Here's the code I wrote for the area sweep algorithm:
#include <iostream>
#include <vector>
using namespace std;
class Rectangle {
public:
int x[2], y[2];
Rectangle(int x1, int y1, int x2, int y2) {
x[0] = x1;
y[0] = y1;
x[1] = x2;
y[1] = y2;
};
void print(void) {
cout << "Rect: " << x[0] << " " << y[0] << " " << x[1] << " " << y[1] << " " <<endl;
};
};
// return the iterator of rec in list
vector<Rectangle *>::iterator bin_search(vector<Rectangle *> &list, int begin, int end, Rectangle *rec) {
cout << begin << " " <<end <<endl;
int mid = (begin+end)/2;
if (list[mid]->y[0] == rec->y[0]) {
if (list[mid]->y[1] == rec->y[1])
return list.begin() + mid;
else if (list[mid]->y[1] < rec->y[1]) {
if (mid == end)
return list.begin() + mid+1;
return bin_search(list,mid+1,mid,rec);
}
else {
if (mid == begin)
return list.begin()+mid;
return bin_search(list,begin,mid-1,rec);
}
}
else if (list[mid]->y[0] < rec->y[0]) {
if (mid == end) {
return list.begin() + mid+1;
}
return bin_search(list, mid+1, end, rec);
}
else {
if (mid == begin) {
return list.begin() + mid;
}
return bin_search(list, begin, mid-1, rec);
}
}
// add rect to rects
void add_rec(Rectangle *rect, vector<Rectangle *> &rects) {
if (rects.size() == 0) {
rects.push_back(rect);
}
else {
vector<Rectangle *>::iterator it = bin_search(rects, 0, rects.size()-1, rect);
rects.insert(it, rect);
}
}
// remove rec from rets
void remove_rec(Rectangle *rect, vector<Rectangle *> &rects) {
vector<Rectangle *>::iterator it = bin_search(rects, 0, rects.size()-1, rect);
rects.erase(it);
}
// calculate the total vertical length covered by rectangles in the active set
int vert_dist(vector<Rectangle *> as) {
int n = as.size();
int totallength = 0;
int start, end;
int i = 0;
while (i < n) {
start = as[i]->y[0];
end = as[i]->y[1];
while (i < n && as[i]->y[0] <= end) {
if (as[i]->y[1] > end) {
end = as[i]->y[1];
}
i++;
}
totallength += end-start;
}
return totallength;
}
bool mycomp1(Rectangle* a, Rectangle* b) {
return (a->x[0] < b->x[0]);
}
bool mycomp2(Rectangle* a, Rectangle* b) {
return (a->x[1] < b->x[1]);
}
int findarea(vector<Rectangle *> rects) {
vector<Rectangle *> start = rects;
vector<Rectangle *> end = rects;
sort(start.begin(), start.end(), mycomp1);
sort(end.begin(), end.end(), mycomp2);
// active set
vector<Rectangle *> as;
int n = rects.size();
int totalarea = 0;
int current = start[0]->x[0];
int next;
int i = 0, j = 0;
// big loop
while (j < n) {
cout << "loop---------------"<<endl;
// add all recs that start at current
while (i < n && start[i]->x[0] == current) {
cout << "add" <<endl;
// add start[i] to AS
add_rec(start[i], as);
cout << "after" <<endl;
i++;
}
// remove all recs that end at current
while (j < n && end[j]->x[1] == current) {
cout << "remove" <<endl;
// remove end[j] from AS
remove_rec(end[j], as);
cout << "after" <<endl;
j++;
}
// find next event x
if (i < n && j < n) {
if (start[i]->x[0] <= end[j]->x[1]) {
next = start[i]->x[0];
}
else {
next = end[j]->x[1];
}
}
else if (j < n) {
next = end[j]->x[1];
}
// distance to next event
int horiz = next - current;
cout << "horiz: " << horiz <<endl;
// figure out vertical dist
int vert = vert_dist(as);
cout << "vert: " << vert <<endl;
totalarea += vert * horiz;
current = next;
}
return totalarea;
}
int main() {
vector<Rectangle *> rects;
rects.push_back(new Rectangle(0,0,1,1));
rects.push_back(new Rectangle(1,0,2,3));
rects.push_back(new Rectangle(0,0,3,3));
rects.push_back(new Rectangle(1,0,5,1));
cout << findarea(rects) <<endl;
}
You can simplify this problem quite a bit if you split each rectangle into smaller rectangles. Collect all of the X and Y coordinates of all the rectangles, and these become your split points - if a rectangle crosses the line, split it in two. When you're done, you have a list of rectangles that overlap either 0% or 100%, if you sort them it should be easy to find the identical ones.
There is a solution listed at the link http://codercareer.blogspot.com/2011/12/no-27-area-of-rectangles.html for finding the total area of multiple rectangles such that the overlapped area is counted only once.
The above solution can be extended to compute only the overlapped area(and that too only once even if the overlapped area is covered by multiple rectangles) with horizontal sweep lines for every pair of consecutive vertical sweep lines.
If aim is just to find out the total area covered by the all the rectangles, then horizontal sweep lines are not needed and just a merge of all the rectangles between two vertical sweep lines would give the area.
On the other hand, if you want to compute the overlapped area only, the horizontal sweep lines are needed to find out how many rectangles are overlapping in between vertical (y1, y2) sweep lines.
Here is the working code for the solution I implemented in Java.
import java.io.*;
import java.util.*;
class Solution {
static class Rectangle{
int x;
int y;
int dx;
int dy;
Rectangle(int x, int y, int dx, int dy){
this.x = x;
this.y = y;
this.dx = dx;
this.dy = dy;
}
Range getBottomLeft(){
return new Range(x, y);
}
Range getTopRight(){
return new Range(x + dx, y + dy);
}
#Override
public int hashCode(){
return (x+y+dx+dy)/4;
}
#Override
public boolean equals(Object other){
Rectangle o = (Rectangle) other;
return o.x == this.x && o.y == this.y && o.dx == this.dx && o.dy == this.dy;
}
#Override
public String toString(){
return String.format("X = %d, Y = %d, dx : %d, dy : %d", x, y, dx, dy);
}
}
static class RW{
Rectangle r;
boolean start;
RW (Rectangle r, boolean start){
this.r = r;
this.start = start;
}
#Override
public int hashCode(){
return r.hashCode() + (start ? 1 : 0);
}
#Override
public boolean equals(Object other){
RW o = (RW)other;
return o.start == this.start && o.r.equals(this.r);
}
#Override
public String toString(){
return "Rectangle : " + r.toString() + ", start = " + this.start;
}
}
static class Range{
int l;
int u;
public Range(int l, int u){
this.l = l;
this.u = u;
}
#Override
public int hashCode(){
return (l+u)/2;
}
#Override
public boolean equals(Object other){
Range o = (Range) other;
return o.l == this.l && o.u == this.u;
}
#Override
public String toString(){
return String.format("L = %d, U = %d", l, u);
}
}
static class XComp implements Comparator<RW>{
#Override
public int compare(RW rw1, RW rw2){
//TODO : revisit these values.
Integer x1 = -1;
Integer x2 = -1;
if(rw1.start){
x1 = rw1.r.x;
}else{
x1 = rw1.r.x + rw1.r.dx;
}
if(rw2.start){
x2 = rw2.r.x;
}else{
x2 = rw2.r.x + rw2.r.dx;
}
return x1.compareTo(x2);
}
}
static class YComp implements Comparator<RW>{
#Override
public int compare(RW rw1, RW rw2){
//TODO : revisit these values.
Integer y1 = -1;
Integer y2 = -1;
if(rw1.start){
y1 = rw1.r.y;
}else{
y1 = rw1.r.y + rw1.r.dy;
}
if(rw2.start){
y2 = rw2.r.y;
}else{
y2 = rw2.r.y + rw2.r.dy;
}
return y1.compareTo(y2);
}
}
public static void main(String []args){
Rectangle [] rects = new Rectangle[4];
rects[0] = new Rectangle(10, 10, 10, 10);
rects[1] = new Rectangle(15, 10, 10, 10);
rects[2] = new Rectangle(20, 10, 10, 10);
rects[3] = new Rectangle(25, 10, 10, 10);
int totalArea = getArea(rects, false);
System.out.println("Total Area : " + totalArea);
int overlapArea = getArea(rects, true);
System.out.println("Overlap Area : " + overlapArea);
}
static int getArea(Rectangle []rects, boolean overlapOrTotal){
printArr(rects);
// step 1: create two wrappers for every rectangle
RW []rws = getWrappers(rects);
printArr(rws);
// steps 2 : sort rectangles by their x-coordinates
Arrays.sort(rws, new XComp());
printArr(rws);
// step 3 : group the rectangles in every range.
Map<Range, List<Rectangle>> rangeGroups = groupRects(rws, true);
for(Range xrange : rangeGroups.keySet()){
List<Rectangle> xRangeRects = rangeGroups.get(xrange);
System.out.println("Range : " + xrange);
System.out.println("Rectangles : ");
for(Rectangle rectx : xRangeRects){
System.out.println("\t" + rectx);
}
}
// step 4 : iterate through each of the pairs and their rectangles
int sum = 0;
for(Range range : rangeGroups.keySet()){
List<Rectangle> rangeRects = rangeGroups.get(range);
sum += getOverlapOrTotalArea(rangeRects, range, overlapOrTotal);
}
return sum;
}
static Map<Range, List<Rectangle>> groupRects(RW []rws, boolean isX){
//group the rws with either x or y coordinates.
Map<Range, List<Rectangle>> rangeGroups = new HashMap<Range, List<Rectangle>>();
List<Rectangle> rangeRects = new ArrayList<Rectangle>();
int i=0;
int prev = Integer.MAX_VALUE;
while(i < rws.length){
int curr = isX ? (rws[i].start ? rws[i].r.x : rws[i].r.x + rws[i].r.dx): (rws[i].start ? rws[i].r.y : rws[i].r.y + rws[i].r.dy);
if(prev < curr){
Range nRange = new Range(prev, curr);
rangeGroups.put(nRange, rangeRects);
rangeRects = new ArrayList<Rectangle>(rangeRects);
}
prev = curr;
if(rws[i].start){
rangeRects.add(rws[i].r);
}else{
rangeRects.remove(rws[i].r);
}
i++;
}
return rangeGroups;
}
static int getOverlapOrTotalArea(List<Rectangle> rangeRects, Range range, boolean isOverlap){
//create horizontal sweep lines similar to vertical ones created above
// Step 1 : create wrappers again
RW []rws = getWrappers(rangeRects);
// steps 2 : sort rectangles by their y-coordinates
Arrays.sort(rws, new YComp());
// step 3 : group the rectangles in every range.
Map<Range, List<Rectangle>> yRangeGroups = groupRects(rws, false);
//step 4 : for every range if there are more than one rectangles then computer their area only once.
int sum = 0;
for(Range yRange : yRangeGroups.keySet()){
List<Rectangle> yRangeRects = yRangeGroups.get(yRange);
if(isOverlap){
if(yRangeRects.size() > 1){
sum += getArea(range, yRange);
}
}else{
if(yRangeRects.size() > 0){
sum += getArea(range, yRange);
}
}
}
return sum;
}
static int getArea(Range r1, Range r2){
return (r2.u-r2.l)*(r1.u-r1.l);
}
static RW[] getWrappers(Rectangle []rects){
RW[] wrappers = new RW[rects.length * 2];
for(int i=0,j=0;i<rects.length;i++, j+=2){
wrappers[j] = new RW(rects[i], true);
wrappers[j+1] = new RW(rects[i], false);
}
return wrappers;
}
static RW[] getWrappers(List<Rectangle> rects){
RW[] wrappers = new RW[rects.size() * 2];
for(int i=0,j=0;i<rects.size();i++, j+=2){
wrappers[j] = new RW(rects.get(i), true);
wrappers[j+1] = new RW(rects.get(i), false);
}
return wrappers;
}
static void printArr(Object []a){
for(int i=0; i < a.length;i++){
System.out.println(a[i]);
}
System.out.println();
}
The following answer should give the total Area only once.
it comes previous answers, but implemented now in C#.
It works also with floats (or double, if you need[it doesn't itterate over the VALUES).
Credits:
http://codercareer.blogspot.co.il/2011/12/no-27-area-of-rectangles.html
EDIT:
The OP asked for the overlapping area - thats obviously very simple:
var totArea = rects.Sum(x => x.Width * x.Height);
and then the answer is:
var overlappingArea =totArea-GetArea(rects)
Here is the code:
#region rectangle overlapping
/// <summary>
/// see algorithm for detecting overlapping areas here: https://stackoverflow.com/a/245245/3225391
/// or easier here:
/// http://codercareer.blogspot.co.il/2011/12/no-27-area-of-rectangles.html
/// </summary>
/// <param name="dim"></param>
/// <returns></returns>
public static float GetArea(RectangleF[] rects)
{
List<float> xs = new List<float>();
foreach (var item in rects)
{
xs.Add(item.X);
xs.Add(item.Right);
}
xs = xs.OrderBy(x => x).Cast<float>().ToList();
rects = rects.OrderBy(rec => rec.X).Cast<RectangleF>().ToArray();
float area = 0f;
for (int i = 0; i < xs.Count - 1; i++)
{
if (xs[i] == xs[i + 1])//not duplicate
continue;
int j = 0;
while (rects[j].Right < xs[i])
j++;
List<Range> rangesOfY = new List<Range>();
var rangeX = new Range(xs[i], xs[i + 1]);
GetRangesOfY(rects, j, rangeX, out rangesOfY);
area += GetRectArea(rangeX, rangesOfY);
}
return area;
}
private static void GetRangesOfY(RectangleF[] rects, int rectIdx, Range rangeX, out List<Range> rangesOfY)
{
rangesOfY = new List<Range>();
for (int j = rectIdx; j < rects.Length; j++)
{
if (rangeX.less < rects[j].Right && rangeX.greater > rects[j].Left)
{
rangesOfY = Range.AddRange(rangesOfY, new Range(rects[j].Top, rects[j].Bottom));
#if DEBUG
Range rectXRange = new Range(rects[j].Left, rects[j].Right);
#endif
}
}
}
static float GetRectArea(Range rangeX, List<Range> rangesOfY)
{
float width = rangeX.greater - rangeX.less,
area = 0;
foreach (var item in rangesOfY)
{
float height = item.greater - item.less;
area += width * height;
}
return area;
}
internal class Range
{
internal static List<Range> AddRange(List<Range> lst, Range rng2add)
{
if (lst.isNullOrEmpty())
{
return new List<Range>() { rng2add };
}
for (int i = lst.Count - 1; i >= 0; i--)
{
var item = lst[i];
if (item.IsOverlapping(rng2add))
{
rng2add.Merge(item);
lst.Remove(item);
}
}
lst.Add(rng2add);
return lst;
}
internal float greater, less;
public override string ToString()
{
return $"ln{less} gtn{greater}";
}
internal Range(float less, float greater)
{
this.less = less;
this.greater = greater;
}
private void Merge(Range rng2add)
{
this.less = Math.Min(rng2add.less, this.less);
this.greater = Math.Max(rng2add.greater, this.greater);
}
private bool IsOverlapping(Range rng2add)
{
return !(less > rng2add.greater || rng2add.less > greater);
//return
// this.greater < rng2add.greater && this.greater > rng2add.less
// || this.less > rng2add.less && this.less < rng2add.greater
// || rng2add.greater < this.greater && rng2add.greater > this.less
// || rng2add.less > this.less && rng2add.less < this.greater;
}
}
#endregion rectangle overlapping
If your rectangles are going to be sparse (mostly not intersecting) then it might be worth a look at recursive dimensional clustering. Otherwise a quad-tree seems to be the way to go (as has been mentioned by other posters.
This is a common problem in collision detection in computer games, so there is no shortage of resources suggesting ways to solve it.
Here is a nice blog post summarizing RCD.
Here is a Dr.Dobbs article summarizing various collision detection algorithms, which would be suitable.
This type of collision detection is often called AABB (Axis Aligned Bounding Boxes), that's a good starting point for a google search.
You can find the overlap on the x and on the y axis and multiply those.
int LineOverlap(int line1a, line1b, line2a, line2b)
{
// assume line1a <= line1b and line2a <= line2b
if (line1a < line2a)
{
if (line1b > line2b)
return line2b-line2a;
else if (line1b > line2a)
return line1b-line2a;
else
return 0;
}
else if (line2a < line1b)
return line2b-line1a;
else
return 0;
}
int RectangleOverlap(Rect rectA, rectB)
{
return LineOverlap(rectA.x1, rectA.x2, rectB.x1, rectB.x2) *
LineOverlap(rectA.y1, rectA.y2, rectB.y1, rectB.y2);
}
I found a different solution than the sweep algorithm.
Since your rectangles are all rectangular placed, the horizontal and vertical lines of the rectangles will form a rectangular irregular grid. You can 'paint' the rectangles on this grid; which means, you can determine which fields of the grid will be filled out. Since the grid lines are formed from the boundaries of the given rectangles, a field in this grid will always either completely empty or completely filled by an rectangle.
I had to solve the problem in Java, so here's my solution: http://pastebin.com/03mss8yf
This function calculates of the complete area occupied by the rectangles. If you are interested only in the 'overlapping' part, you must extend the code block between lines 70 and 72. Maybe you can use a second set to store which grid fields are used more than once. Your code between line 70 and 72 should be replaced with a block like:
GridLocation gl = new GridLocation(curX, curY);
if(usedLocations.contains(gl) && usedLocations2.add(gl)) {
ret += width*height;
} else {
usedLocations.add(gl);
}
The variable usedLocations2 here is of the same type as usedLocations; it will be constructed
at the same point.
I'm not really familiar with complexity calculations; so I don't know which of the two solutions (sweep or my grid solution) will perform/scale better.
Considering we have two rectangles (A and B) and we have their bottom left (x1,y1) and top right (x2,y2) coordination. The Using following piece of code you can calculate the overlapped area in C++.
#include <iostream>
using namespace std;
int rectoverlap (int ax1, int ay1, int ax2, int ay2, int bx1, int by1, int bx2, int by2)
{
int width, heigh, area;
if (ax2<bx1 || ay2<by1 || ax1>bx2 || ay1>by2) {
cout << "Rectangles are not overlapped" << endl;
return 0;
}
if (ax2>=bx2 && bx1>=ax1){
width=bx2-bx1;
heigh=by2-by1;
} else if (bx2>=ax2 && ax1>=bx1) {
width=ax2-ax1;
heigh=ay2-ay1;
} else {
if (ax2>bx2){
width=bx2-ax1;
} else {
width=ax2-bx1;
}
if (ay2>by2){
heigh=by2-ay1;
} else {
heigh=ay2-by1;
}
}
area= heigh*width;
return (area);
}
int main()
{
int ax1,ay1,ax2,ay2,bx1,by1,bx2,by2;
cout << "Inter the x value for bottom left for rectangle A" << endl;
cin >> ax1;
cout << "Inter the y value for bottom left for rectangle A" << endl;
cin >> ay1;
cout << "Inter the x value for top right for rectangle A" << endl;
cin >> ax2;
cout << "Inter the y value for top right for rectangle A" << endl;
cin >> ay2;
cout << "Inter the x value for bottom left for rectangle B" << endl;
cin >> bx1;
cout << "Inter the y value for bottom left for rectangle B" << endl;
cin >> by1;
cout << "Inter the x value for top right for rectangle B" << endl;
cin >> bx2;
cout << "Inter the y value for top right for rectangle B" << endl;
cin >> by2;
cout << "The overlapped area is " << rectoverlap (ax1, ay1, ax2, ay2, bx1, by1, bx2, by2) << endl;
}
The post by user3048546 contains an error in the logic on lines 12-17. Here is a working implementation:
int rectoverlap (int ax1, int ay1, int ax2, int ay2, int bx1, int by1, int bx2, int by2)
{
int width, height, area;
if (ax2<bx1 || ay2<by1 || ax1>bx2 || ay1>by2) {
cout << "Rectangles are not overlapped" << endl;
return 0;
}
if (ax2>=bx2 && bx1>=ax1){
width=bx2-bx1;
} else if (bx2>=ax2 && ax1>=bx1) {
width=ax2-ax1;
} else if (ax2>bx2) {
width=bx2-ax1;
} else {
width=ax2-bx1;
}
if (ay2>=by2 && by1>=ay1){
height=by2-by1;
} else if (by2>=ay2 && ay1>=by1) {
height=ay2-ay1;
} else if (ay2>by2) {
height=by2-ay1;
} else {
height=ay2-by1;
}
area = heigh*width;
return (area);
}

Resources