矩形的坐标是旋转前的坐标:
矩形A(left1,top1,width1,height1,angle1)
矩形B(left2,top2,width2,height2,angle2)
遍历A的所有边,并与B的所有边做相交判断。该方法适用于所有的多边形相交碰撞检测问题
相交的定义为:重叠部分的面积>0,故相切情况不属于相交
不属于相交情况下,再判断包含的情况:
取矩阵A中心点P,判断P是否在矩阵B内部,同理判断矩阵B中心点P’是否在矩阵A内部,
两次判断只要有一次属于内部即可判断处于包含情况。
注意: 共线情况可以通过跨立实验,不过在快速排斥阶段就被剔除了
// 判断线段AB 和线段CD 是否相交
function judgeSegmentsIntersect (A, B, C, D) {
//快速排斥, 不考虑相切情况 判断时要算上等于
if (Math.max(C.x, D.x) <= Math.min(A.x, B.x) || Math.max(C.y, D.y) <= Math.min(A.y, B.y) ||
Math.max(A.x, B.x) <= Math.min(C.x, D.x) || Math.max(A.y, B.y) <= Math.min(C.y, D.y)) {
return false
}
// 向量叉乘
const crossMul = (v1, v2) => {
return v1.x * v2.y - v1.y * v2.x
}
const vector = (start, end) => {
return {
x: end.x - start.x,
y: end.y - start.y
}
}
let AC = vector(A, C)
let AD = vector(A, D)
let BC = vector(B, C)
let BD = vector(B, D)
let CA = vector(C, A)
let DA = vector(D, A)
let CB = vector(C, B)
let DB = vector(D, B)
return (crossMul(AC, AD) * crossMul(BC, BD) <= 0)
&& (crossMul(CA, CB) * crossMul(DA, DB) <= 0)
}
// 绕原点逆时针旋转后的点坐标
// 默认绕原点旋转
const rotate = ({ x, y }, deg, origin = { x: 0, y: 0 }) => ({
x: (x - origin.x) * Math.cos(deg) + (y - origin.y) * Math.sin(deg) + origin.x,
y: (origin.x - x) * Math.sin(deg) + (y - origin.y) * Math.cos(deg) + origin.y
})
const toDeg = (angle) => angle / 180 * Math.PI
const getCenterPoint = (box) => ({
x: box.left + box.width / 2,
y: box.top + box.height / 2
})
/**
* 转化为顶点坐标数组
* @param {Object} box
*/
function toRect (box) {
let deg = toDeg(box.angle)
let cp = getCenterPoint(box)
return [rotate({
x: box.left,
y: box.top
}, deg, cp), rotate({
x: box.left + box.width,
y: box.top,
}, deg, cp), rotate({
x: box.left + box.width,
y: box.top + box.height,
}, deg, cp), rotate({
x: box.left,
y: box.top + box.height
}, deg, cp)]
}
/**
* 判断矩形相交
*/
function judgeRectanglesIntersect (box1, box2) {
let rect1 = toRect(box1)
let rect2 = toRect(box2)
for (let i = 0; i < rect1.length; i++) {
let A = rect1[i]
let B = i === rect1.length - 1 ? rect1[0] : rect1[i + 1]
for (let j = 0; j < rect2.length; j++) {
let C = rect2[j]
let D = j === rect2.length - 1 ? rect2[0] : rect2[j + 1]
if (judgeSegmentsIntersect(A, B, C, D)) {
return true
}
}
}
return false
}
/**
* 已知两矩形不相交
* 判断矩形是否属于包含关系
* @param {*} rect1
* @param {*} rect2
*/
function judgeRectanglesContain (box1, box2) {
const getCenterPoint = (box) => ({
x: box.left + box.width / 2,
y: box.top + box.height / 2
})
let p1 = getCenterPoint(box1)
let p2 = getCenterPoint(box2)
// 点P需要绕另一个点P'逆时针旋转得到新的位置
let np1 = rotate(p1, toDeg(box2.angle), p2)
let np2 = rotate(p2, toDeg(box1.angle), p1)
// 判断点P是否在水平坐标系的矩形box中
const isInside = (p, { left, top, width, height }) => {
return p.x >= left && p.x <= left + width && p.y >= top && p.y <= top + height
}
return isInside(np1, box2) || isInside(np2, box1)
}
计算所有顶点的横纵坐标均值,记作中心点,计算中心点到每个点的单位向量,以x轴正方向为起始边,按照顺时针方向扫描360度,对扫描到的点进行排序,先考虑从180度到360度,y>0,x从-1到1递增,对于从0到180度,y<0,x从1到-1递减。然后计算三角形面积(利用叉积) 最后将三角形面积求和。
参考文章 矩形旋转碰撞,OBB方向包围盒算法实现
简单说 包围盒 就是用一个方便分离轴的规则形状去包围一个物体
而 分离轴定律 即是根据两个多边形在所有轴上的投影是否重叠判断是否碰撞
如何检测轴投影是否重叠?有两种方法。
/**
* 计算投影半径
* @param {Array(Number)} checkAxis 检测轴 [cosθ,sinθ]
* @param {Array} axis 目标轴 [x,y]
*/
function getProjectionRadius (checkAxis, axis) {
return Math.abs(axis[0] * checkAxis[0] + axis[1] * checkAxis[1])
}
全部代码如下:
// 绕原点逆时针旋转后的点坐标
// 默认绕原点旋转
const rotate = ({ x, y }, deg, origin = { x: 0, y: 0 }) => ({
x: (x - origin.x) * Math.cos(deg) + (y - origin.y) * Math.sin(deg) + origin.x,
y: (origin.x - x) * Math.sin(deg) + (y - origin.y) * Math.cos(deg) + origin.y
})
const toDeg = (angle) => angle / 180 * Math.PI
const getCenterPoint = (box) => ({
x: box.left + box.width / 2,
y: box.top + box.height / 2
})
/**
* 转化为顶点坐标数组
* @param {Object} box
*/
function toRect (box) {
let deg = toDeg(box.angle)
let cp = getCenterPoint(box)
return [rotate({
x: box.left,
y: box.top
}, deg, cp), rotate({
x: box.left + box.width,
y: box.top,
}, deg, cp), rotate({
x: box.left + box.width,
y: box.top + box.height,
}, deg, cp), rotate({
x: box.left,
y: box.top + box.height
}, deg, cp)]
}
/**
* 计算投影半径
* @param {Array(Number)} checkAxis 检测轴 [cosθ,sinθ]
* @param {Array} axis 目标轴 [x,y]
*/
function getProjectionRadius (checkAxis, axis) {
return Math.abs(axis[0] * checkAxis[0] + axis[1] * checkAxis[1])
}
/**
* 判断是否碰撞
* @param {Array} rect1 矩形顶点坐标数组 [Pa,Pb,Pc,Pd]
* @param {*} rect2
*/
function isCollision (box1, box2) {
let rect1 = toRect(box1)
let rect2 = toRect(box2)
const vector = (start, end) => {
return [end.x - start.x, end.y - start.y]
}
// 两个矩形的中心点
const p1 = getCenterPoint(box1)
const p2 = getCenterPoint(box2)
//向量 p1p2
const vp1p2 = vector(p1, p2)
//矩形1的两边向量
let AB = vector(rect1[0], rect1[1])
let BC = vector(rect1[1], rect1[2])
//矩形2的两边向量
let A1B1 = vector(rect2[0], rect2[1])
let B1C1 = vector(rect2[1], rect2[2])
// 矩形1 的两个弧度
let deg11 = toDeg(box1.angle)
let deg12 = toDeg(90 - box1.angle)
// 矩形2 的两个弧度
let deg21 = toDeg(box2.angle)
let deg22 = toDeg(90 - box2.angle)
// 投影重叠
const isCover = (checkAxisRadius, deg, targetAxis1, targetAxis2) => {
let checkAxis = [Math.cos(deg), Math.sin(deg)]
let targetAxisRadius = (getProjectionRadius(checkAxis, targetAxis1) + getProjectionRadius(checkAxis, targetAxis2)) / 2
let centerPointRadius = getProjectionRadius(checkAxis, vp1p2)
console.log(`checkAxis:${checkAxis},三个投影:${checkAxisRadius}, ${targetAxisRadius}, ${centerPointRadius}`)
return checkAxisRadius + targetAxisRadius > centerPointRadius
}
return isCover(box1.width / 2, deg11, A1B1, B1C1) &&
isCover(box1.height / 2, deg12, A1B1, B1C1) &&
isCover(box2.width / 2, deg21, AB, BC) &&
isCover(box2.height / 2, deg22, AB, BC)
}
(function main () {
let box1 = {
left: 0,
top: 0,
width: 100,
height: 100,
angle: 30
}
let box2 = {
left: 100,
top: 0,
width: 100,
height: 100,
angle: 0
}
return isCollision(box1, box2)
})()