I am working on a collision detection system where a moving rectangle (movingRect) collides with a stationary rectangle (steadyRect). Upon collision, I want to calculate an offset that should be applied to the steady rectangle to resolve the collision. The goal is to push the steady rectangle by the minimal amount needed to separate it from the moving rectangle, while keeping the direction of the offset consistent with their relative positions.
Here’s the function I’m currently using. COLLISION_THRESHOLD is the minimum distance between elements that will not trigger the collision. If the element moves closer, the collision is registered.
function getCollisionOffset(movingRect: DOMRect, steadyRect: DOMRect) {
const movingCenter = {
x: movingRect.left + movingRect.width / 2,
y: movingRect.top + movingRect.height / 2
};
const steadyCenter = {
x: steadyRect.left + steadyRect.width / 2,
y: steadyRect.top + steadyRect.height / 2
};
const vector = {
x: steadyCenter.x - movingCenter.x,
y: steadyCenter.y - movingCenter.y
};
const b1 = Math.abs(vector.x);
const b2 = (movingRect.width / 2) + (steadyRect.width / 2) + COLLISION_THRESHOLD;
const c1 = Math.abs(vector.y);
const c2 = (movingRect.height / 2) + (steadyRect.height / 2) + COLLISION_THRESHOLD
return {
x: (Math.abs(vector.x) / b2 * b1) * Math.sign(vector.x),
y: (Math.abs(vector.y) / c2 * c1) * Math.sign(vector.y)
};
}
It offsets the element in the right direction, but the offset is always too big.
What you need is to find the smallest distance required move the rectangle to prevent overlap. The simplest way is to find each of the cardinal vectors needed to resolve the collision, and then pick the smallest of those
const rect1Left = movingRect.left;
const rect1Right = movingRect.left + movingRect.width;
const rect2Left = steadyRect.left;
const rect2Right = steadyRect.left + steadyRect.width;
let xSolveRect = 0;
if
(
(rect2Left < rect1Right && rect2Right > rect1Left) || // left-side overlap
(rect2Right > rect1Left && rect2Left < rect1Right) || // right-side overlap
(rect2Left > rect1Left && rect2Right < rect1Right) || // rect2 inside of rect1
(rect1Left > rect2Left && rect1Right < rect2Right) // rect1 inside of rect2
)
{
// there is horizontal overlap
const xMoveRight = rect1Right - rect2Left;
const xMoveLeft = rect2Right - rect1Left;
xSolveRect = (xMoveRight < xMoveLeft && xMoveRight > 0) ? xMoveRight : -xMoveLeft;
}
//
// same concept for vertical:
const ySolveRect = 0;
// fill in vertical solve here
return {
x: xSolveRect,
y: ySolveRect
};