Here is a small separation and rest algorithm that you could try.
I assume that as soon as you can quickly list each pair of colliding rectangles, you can also check to see if it is completely contained in another in constant time.
So, we need to find oncoming rectangles.
First, summarize it as follows: suppose you have two sets of rectangles A and B , and that you want to find only pairs (a, b) , so that rectangle A is equal to A , B is equal to B , and A and B
First of all, an idea. Consider the following example of two groups A and B rectangles partially separated by a horizontal line L :
+----+ +-----+ | A1 | | B1 | | | +-----+ +-----+ +----+ | A2 | +-----+ +-----+ | A3 | _____________________________|_____|________ L | | +-------------------+##+ | | |##| | | B2 +##|--+ | | +----------------------+
Line L divides the sets A and B into three subsets:
A above L: {A1, A2} (short: A>L) A intersects L: {A3} (short: A=L) A below L: {} (short: A<L) B above L: {B1} (B>L) B intersects L: {} (B=L) B below L: {B2} (B<L)
Please note that only rectangles from the following groups can collide:
A<LA=L A>L B<L yy N B=L yyy B>LN yy
That is, if we want to find all collisions between A and B , as soon as we find a suitable line L , we can ignore the collisions between A<L against B>L and A>L against B<L . Thus, we get the following division and rest algorithm: while A and B not empty, find a suitable line that (approximately) maximizes the number of remote collision checks, subdivides A and B into three groups each, recursively proceed to seven subgroup collisions, ignore two combinations of subgroups.
Assuming that if the rectangles are “small” and the groups A=L and B=L are mostly empty, this will (roughly) reduce the size of the sets in half at each step, and thus we get an algorithm that on average works somehow like O(n*log(n)) instead of O(n*n) .
Once you have a general case for arbitrary A and B , take the whole set of rectangles R and run the algorithm using A = R; B = R A = R; B = R
Here is an approximate sketch in Python:
def isSubinterval(aStart, aEnd, bStart, bEnd): return aStart >= bStart and aEnd <= bEnd def intersects(aStart, aEnd, bStart, bEnd): return not (aEnd < bStart or aStart > bEnd) class Rectangle: def __init__(self, l, r, b, t): self.left = l self.right = r self.bottom = b self.top = t def isSubrectangle(self, other): return ( isSubinterval(self.left, self.right, other.left, other.right) and isSubinterval(self.bottom, self.top, other.bottom, other.top) ) def intersects(self, other): return ( intersects(self.left, self.right, other.left, other.right) and intersects(self.bottom, self.top, other.bottom, other.top) ) def __repr__(self): return ("[%f,%f]x[%f,%f]" % (self.left, self.right, self.bottom, self.top)) def boundingBox(rects): infty = float('inf') b = infty t = - infty l = infty r = - infty for rect in rects: b = min(b, rect.bottom) l = min(l, rect.left) r = max(r, rect.right) t = max(t, rect.top) return Rectangle(l, r, b, t) class DividingLine: def __init__(self, isHorizontal, position): self.isHorizontal = isHorizontal self.position = position def isAbove(self, rectangle): if self.isHorizontal: return rectangle.bottom > self.position else: return rectangle.left > self.position def isBelow(self, rectangle): if self.isHorizontal: return rectangle.top < self.position else: return rectangle.right < self.position def enumeratePossibleLines(boundingBox): NUM_TRIED_LINES = 5 for i in range(1, NUM_TRIED_LINES + 1): w = boundingBox.right - boundingBox.left yield DividingLine(False, boundingBox.left + w / float(NUM_TRIED_LINES + 1) * i) h = boundingBox.top - boundingBox.bottom yield DividingLine(True, boundingBox.bottom + h / float(NUM_TRIED_LINES + 1) * i) def findGoodDividingLine(rects_1, rects_2): bb = boundingBox(rects_1 + rects_2) bestLine = None bestGain = 0 for line in enumeratePossibleLines(bb): above_1 = len([r for r in rects_1 if line.isAbove(r)]) below_1 = len([r for r in rects_1 if line.isBelow(r)]) above_2 = len([r for r in rects_2 if line.isAbove(r)]) below_2 = len([r for r in rects_2 if line.isBelow(r)])
Here is a small demo:
rects = [ Rectangle(1,6,9,10), Rectangle(4,7,6,10), Rectangle(1,5,6,7), Rectangle(8,9,8,10), Rectangle(6,9,5,7), Rectangle(8,9,1,6), Rectangle(7,9,2,4), Rectangle(2,8,2,3), Rectangle(1,3,1,4) ] def showInterestingCollision(a, b): if a is not b: if a.left < b.left: print("%r <-> %r collision" % (a, b)) collideAllVsAll(rects, rects, showInterestingCollision)
At least in this case, it really detects all the interesting collisions:
[1.000000,6.000000]x[9.000000,10.000000] <-> [4.000000,7.000000]x[6.000000,10.000000] collision [1.000000,5.000000]x[6.000000,7.000000] <-> [4.000000,7.000000]x[6.000000,10.000000] collision [4.000000,7.000000]x[6.000000,10.000000] <-> [6.000000,9.000000]x[5.000000,7.000000] collision [6.000000,9.000000]x[5.000000,7.000000] <-> [8.000000,9.000000]x[1.000000,6.000000] collision [7.000000,9.000000]x[2.000000,4.000000] <-> [8.000000,9.000000]x[1.000000,6.000000] collision [2.000000,8.000000]x[2.000000,3.000000] <-> [8.000000,9.000000]x[1.000000,6.000000] collision [2.000000,8.000000]x[2.000000,3.000000] <-> [7.000000,9.000000]x[2.000000,4.000000] collision [1.000000,3.000000]x[1.000000,4.000000] <-> [2.000000,8.000000]x[2.000000,3.000000] collision
Here is a slightly more realistic demo:
from random import random from matplotlib import pyplot as plt def randomRect(): w = random() * 0.1 h = random() * 0.1 centerX = random() * (1 - w) centerY = random() * (1 - h) return Rectangle( centerX - w/2, centerX + w/2, centerY - h/2, centerY + h/2 ) randomRects = [randomRect() for _ in range(0, 500)] for r in randomRects: plt.fill( [r.left, r.right, r.right, r.left], [r.bottom, r.bottom, r.top, r.top], 'b-', color = 'k', fill = False ) def markSubrectanglesRed(a, b): if a is not b: if a.isSubrectangle(b): plt.fill( [a.left, a.right, a.right, a.left], [a.bottom, a.bottom, a.top, a.top], 'b-', color = 'r', alpha = 0.4 ) plt.fill( [b.left, b.right, b.right, b.left], [b.bottom, b.bottom, b.top, b.top], 'b-', color = 'b', fill = False ) collideAllVsAll(randomRects, randomRects, markSubrectanglesRed) plt.show()
The graph shows all the selected rectangles in red, and the rectangles enclosed in blue:

Here is a visualization of bounding rectangles (yellow) and selected dividing lines (blue) of a quasi-two-dimensional partition of space for a small example with one collision:

For 10,000 “reasonable sizes” of random rectangles (with an intersection speed of about the same as in the image), it calculates all collisions in 18 seconds, although the code is very far from optimization.