Created
June 11, 2020 16:09
-
-
Save rokoroku/23f4ea770982f69cf01c749f2a89637c to your computer and use it in GitHub Desktop.
Javascript multiple rectangle difference
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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