Skip to content

Instantly share code, notes, and snippets.

@rokoroku
Created June 11, 2020 16:09
Show Gist options
  • Save rokoroku/23f4ea770982f69cf01c749f2a89637c to your computer and use it in GitHub Desktop.
Save rokoroku/23f4ea770982f69cf01c749f2a89637c to your computer and use it in GitHub Desktop.
Javascript multiple rectangle difference
// Rectangle diff algorithm
// https://en.wikibooks.org/wiki/Algorithm_Implementation/Geometry/Rectangle_difference
type Rect = Pick<ClientRect, 'top' | 'left' | 'width' | 'height'>;
/**
* Checks if the first rectangle contains the second.
*
* @param rectA first rectangle
* @param rectB second rectangle
* @return <code>true</code> if <code>rectA</code> contains <code>rectB</code>
*/
function contains(rectA: Rect, rectB: Rect) {
return (
rectB.left >= rectA.left &&
rectB.top >= rectA.top &&
rectB.left + rectB.width <= rectA.left + rectA.width &&
rectB.top + rectB.height <= rectA.top + rectA.height
);
}
/**
* Checks if two rectangles intersect
*
* @param rectA first rectangle
* @param rectB second rectangle
* @return <code>true</code> if <code>rectA</code> and <code>rectB</code> intersect
*/
function intersectsRect(rectA: Rect, rectB: Rect) {
return !(
rectB.left + rectB.width <= rectA.left ||
rectB.top + rectB.height <= rectA.top ||
rectB.left >= rectA.left + rectA.width ||
rectB.top >= rectA.top + rectA.height
);
}
function differenceRect(rectA: Rect, rectB: Rect): Rect[] {
if (rectB == null || !intersectsRect(rectA, rectB) || contains(rectB, rectA)) {
return [];
}
let topRect: Rect = null;
let bottomRect: Rect = null;
let leftRect: Rect = null;
let rightRect: Rect = null;
//compute the top rectangle
const raHeight = rectB.top - rectA.top;
if (raHeight > 0) {
topRect = {
left: rectA.left,
top: rectA.top,
width: rectA.width,
height: raHeight
};
}
//compute the bottom rectangle
const rbY = rectB.top + rectB.height;
const rbHeight = rectA.height - (rbY - rectA.top);
if (rbHeight > 0 && rbY < rectA.top + rectA.height) {
bottomRect = {
left: rectA.left,
top: rbY,
width: rectA.width,
height: rbHeight
};
}
const rectAYH = rectA.top + rectA.height;
const y1 = rectB.top > rectA.top ? rectB.top : rectA.top;
const y2 = rbY < rectAYH ? rbY : rectAYH;
const rcHeight = y2 - y1;
//compute the left rectangle
const rcWidth = rectB.left - rectA.left;
if (rcWidth > 0 && rcHeight > 0) {
leftRect = { left: rectA.left, top: y1, width: rcWidth, height: rcHeight };
}
//compute the right rectangle
const rbX = rectB.left + rectB.width;
const rdWidth = rectA.width - (rbX - rectA.left);
if (rdWidth > 0) {
rightRect = { left: rbX, top: y1, width: rdWidth, height: rcHeight };
}
return [topRect, leftRect, rightRect, bottomRect].filter(Boolean);
}
function differenceRects(...rects: Rect[]) {
return rects.reduce((results, current) => {
if (results.length === 0) {
return results.concat(current);
} else {
const intersections = results.filter((rect) => intersectsRect(rect, current));
const diffRects = intersections.map((rect) => differenceRect(rect, current));
return results.filter((rect) => !intersections.includes(rect)).concat(...diffRects);
}
}, []);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment