Created a Javascript solution based on Biggy Smith's answer.
Uses no external libraries. All input arguments are numbers.
// cX = circle center x
// cY = circle center y
// rX = rectangle top left x
// rY = rectangle top left y
// rW = rectangle width
// rH = rectangle height
// rA = rectangle rotation amount around center (in radians)
function getCollisionNormal(cX, cY, rX, rY, rW, rH, rA) {
const eX = rW / 2;
const eY = rH / 2;
const rCX = rX + eX;
const rCY = rY + eY;
const uxX = Math.cos(rA);
const uxY = Math.sin(rA);
const uyX = Math.cos(rA - 1.571)
const uyY = Math.sin(rA - 1.571);
const distanceX = cX - rCX;
const distanceY = cY - rCY;
let dx = distanceX * uxX + distanceY * uxY;
let dy = distanceX * uyX + distanceY * uyY;
if (dx > eX) { dx = eX; }
if (dx < -eX) { dx = -eX; }
if (dy > eY) { dy = eY; }
if (dy < -eY) { dy = -eY; }
const pX = rCX + dx * uxX + dy * uyX;
const pY = rCY + dx * uxY + dy * uyY;
const deltaX = cX - pX;
const deltaY = cY - pY;
const magnitude = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
if (magnitude === 0) {
return { x: 0, y: 0, xDir: 0, yDir: 0 };
} else {
return { x: pX, y: pY, xDir: deltaX / magnitude, yDir: deltaY / magnitude };
}
}