我将使用JavaScript来演示这个过程(并提供代码),但是可以用任何语言来完成。
链接:
- [Codepen上的最终演示](link1)
- [GitHub存储库](link2)
概念:
为了实现这一点,我们将在另一个矩形的两个轴(X和Y)上使用角投影。只有当一个矩形的四个投影与另一个矩形相交时,这两个矩形才会发生碰撞:
- 蓝色矩形的角在橙色矩形的X轴上
- 蓝色矩形的角在橙色矩形的Y轴上
- 橙色矩形的角在蓝色矩形的X轴上
- 橙色矩形的角在蓝色矩形的Y轴上
过程
1- 找到矩形轴
首先创建两个向量,代表从矩形中心到X轴(OX)和Y轴(OY),然后将它们旋转以与矩形轴对齐。
维基百科关于旋转二维向量的文章
const getAxis = (rect) => {
const OX = new Vector({x:1, y:0});
const OY = new Vector({x:0, y:1});
const RX = OX.Rotate(rect.angle * Math.PI / 180);
const RY = OY.Rotate(rect.angle * Math.PI / 180);
return [
new Line({...rect.center, dx: RX.x, dy: RX.y}),
new Line({...rect.center, dx: RY.x, dy: RY.y}),
];
}
Vector是一个简单的x,y对象。
class Vector {
constructor({x=0,y=0}={}) {
this.x = x;
this.y = y;
}
Rotate(theta) {
return new Vector({
x: this.x * Math.cos(theta) - this.y * Math.sin(theta),
y: this.x * Math.sin(theta) + this.y * Math.cos(theta),
});
}
}
线段由两个向量表示:
- 起点:表示起始位置的向量
- 方向:表示单位方向的向量
class Line {
constructor({x=0,y=0, dx=0, dy=0}) {
this.origin = new Vector({x,y});
this.direction = new Vector({x:dx,y:dy});
}
}
步骤结果
2- 使用矩形轴获取角落
首先,我们希望扩展我们的轴(我们以1像素/单位大小为基准),以便获得宽度的一半(对于X轴)和高度的一半(对于Y轴),通过添加它们(或它们的相反数),我们可以获得所有的角落。
const getCorners = (rect) => {
const axis = getAxis(rect);
const RX = axis[0].direction.Multiply(rect.w/2);
const RY = axis[1].direction.Multiply(rect.h/2);
return [
rect.center.Add(RX).Add(RY),
rect.center.Add(RX).Add(RY.Multiply(-1)),
rect.center.Add(RX.Multiply(-1)).Add(RY.Multiply(-1)),
rect.center.Add(RX.Multiply(-1)).Add(RY),
]
}
使用这两种新的方法来处理向量:
Add(factor) {
const f = typeof factor === 'object'
? { x:0, y:0, ...factor}
: {x:factor, y:factor}
return new Vector({
x: this.x + f.x,
y: this.y + f.y,
})
}
Multiply(factor) {
const f = typeof factor === 'object'
? { x:0, y:0, ...factor}
: {x:factor, y:factor}
return new Vector({
x: this.x * f.x,
y: this.y * f.y,
})
}
步骤结果
3- 获取角点投影
对于矩形的每个角点,获取另一个矩形在两个轴上的投影坐标。
只需将此函数添加到向量类中即可:
Project(line) {
let dotvalue = line.direction.x * (this.x - line.origin.x)
+ line.direction.y * (this.y - line.origin.y);
return new Vector({
x: line.origin.x + line.direction.x * dotvalue,
y: line.origin.y + line.direction.y * dotvalue,
})
}
(特别感谢Mbo提供了解决方案来获取投影。)
步骤结果
4- 选择投影上的外角
为了按矩形轴排序所有投影点并获取外部投影点,我们可以采取以下步骤:
- 创建一个向量来表示:从矩形中心到投影角的向量
- 使用向量大小函数来获取距离。
get magnitude() {
return Math.sqrt(this.x * this.x + this.y * this.y);
}
使用
点乘来判断向量是否面向与轴相同方向或相反方向(当有符号距离为负时)。
getSignedDistance = (rect, line, corner) => {
const projected = corner.Project(line);
const CP = projected.Minus(rect.center);
const sign = (CP.x * line.direction.x) + (CP.y * line.direction.y) > 0;
const signedDistance = CP.magnitude * (sign ? 1 : -1);
}
然后,通过使用一个简单的循环和最小/最大值的测试,我们可以找到两个外角。它们之间的线段是一个矩形在另一个轴上的投影。
步骤结果
5- 最终:所有的投影都命中矩形吗?
通过沿轴线进行简单的一维测试,我们可以知道它们是否命中。
const isProjectionHit = (minSignedDistance < 0 && maxSignedDistance > 0
|| Math.abs(minSignedDistance) < rectHalfSize
|| Math.abs(maxSignedDistance) < rectHalfSize);
完成
测试所有4个投影将为您提供最终结果。=] !!
希望这个回答能够帮助尽可能多的人。欢迎任何评论。