Reputation: 9092
I'm trying to write a function that takes two overlapping rectangles and returns an array of rectangles that cover the area of rectangle A, but exclude area of rectangle B. I'm having a hard time figuring out what this algorithm looks like as the number of possible collisions is huge and difficult to account for.
tl;dr I'm trying to clip a rectangle using another rectangle resulting in a collection of rectangles covering the remaining area.
|-------------| |-------------|
|A | |R1 |
| |-------|----| |-----|-------|
| |B | | To |R2 |
| | | | ====> | |
| | | | | |
|-----|-------| | |-----|
| |
|------------|
POSSIBLE OVERLAP PATTERNS
|-----| |-----| |-----| |-----|
| |---|-| |-|---| | | |-| | | |-| |
|-|---| | | |---|-| |-|-|-| | |-| |
|-----| |-----| |-| |-----|
|-| |-----| |-----|
|-|-|-| | |---|-| |-|---| |
| |-| | | |---|-| |-|---| |
|-----| |-----| |-----|
Note that the possible overlap patterns is double that shown because rectangle A and B could be ether rectangle in any of the overlap patterns above.
Upvotes: 8
Views: 1511
Reputation: 21
Working from dan.p's code, using the suggestion by boisvert, my routine looks like this:
this.clip = function(other) {
var res = [];
// Rect has a constructor accepting (left, top, [right, bottom]) for historical reasons
if (this.top < other.top) {
res.push(new Rect(Math.max(this.left, other.left), other.top, [Math.min(this.right, other.right), this.top]));
}
if (this.left > other.left) {
res.push(new Rect(other.left, other.top, [this.left, other.bot]));
}
if (this.right < other.right) {
res.push(new Rect(this.right, other.top, [other.right, other.bot]));
}
if (this.bot > other.bot) {
res.push(new Rect(Math.max(this.left, other.left), this.bot, [Math.min(this.right, other.right), other.bot]));
}
return res;
}
I tested with 16 cases (for four independent ifs).
Upvotes: 1
Reputation: 3729
The two rectangles divide the screen in 9 zones (not 14). Think again of your configurations:
y1 -> |-------------|
|A |
y2 -> | |-------|----|
| |B | |
| | | |
| | | |
y3 -> |-----|-------| |
| |
y4 -> |------------|
^ ^ ^ ^
x1 x2 x3 x4
The x coordinates define 5 vertical bands, but the first (left) and last (right) are uninteresting, so you only need to work on the 3 bands from x1 to x4. Same for y coordinates: three horizontal bands from y1 to y4.
So that's 9 rectangular zones that belong to A, B, none or both. Your example is divided like this:
|-----|-------|----|
|A |A |none|
|-----|-------|----|
|A |Both |B |
| | | |
| | | |
|-----|-------|----|
|none |B |B |
|-----|-------|----|
So comparing the coordinates of A and B, you will find which of the 9 zones belong to only A. They are the zones to keep.
Upvotes: 5
Reputation: 416
There will not be an unique solution for any particular setup, but you can easily find one of the solutions with this algorithm:
In total, you will get from 0 to 4 rectangles.
Pseudocode with a somewhat unusual, but for this purpose clear, definition of rectangle:
function getClipped(A, B) {
var rectangles = [];
if (A.top < B.top) {
rectangles.push({ left: A.left, top: A.top, right: A.right, bottom: B.top });
}
if (A.left < B.left) {
rectangles.push({ left: A.left, top: max(A.top, B.top), right: B.left, bottom: min(A.bottom, B.bottom) });
}
if (A.right > B.right) {
rectangles.push({ left: B.right, top: max(A.top, B.top), right: A.right, bottom: min(A.bottom, B.bottom) });
}
if (A.bottom > B.bottom) {
rectangles.push({ left: A.left, top: B.bottom, right: A.right, bottom. A.bottom });
}
return rectangles;
}
var rectA = { left: nn, top: nn, right: nn, bottom: nn};
var rectB = { left: nn, top: nn, right: nn, bottom: nn};
var clipped = getClipped(rectA, rectB) ;
Upvotes: 4