Art*_*hur 4 javascript math geometry collision-detection rectangles
在多次看到这个问题并用旧的(且不可用的)代码回复后,我决定重做所有内容并发布有关它的信息。
矩形的定义如下:
center:x以及y他的位置(记住 0;0 是左上,所以 Y 向下)size:x以及y他的尺寸angle其旋转(以度为单位,0 度是沿着 OX 轴并顺时针旋转)目标是知道两个矩形是否发生碰撞。
Art*_*hur 12
我将使用 Javascript 来演示这个(并提供代码),但我可以按照该过程对每种语言进行操作。
链接
概念
为了实现这一点,我们将在另一个矩形 2 轴(X 和 Y)上使用角投影。仅当一个矩形上的 4 个投影撞击其他矩形时,这 2 个矩形才会发生碰撞:
过程
1-找到矩形轴
首先为轴 0;0(矩形中心)创建 2 个向量到 X (OX) 和 Y (OY),然后旋转它们以与矩形轴对齐。
const getAxis = (rect) => {
const OX = new Vector({x:1, y:0});
const OY = new Vector({x:0, y:1});
// Do not forget to transform degree to radian
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}),
];
}
Run Code Online (Sandbox Code Playgroud)
其中 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),
});
}
}
Run Code Online (Sandbox Code Playgroud)
Line 使用 2 个向量表示斜率:
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});
}
}
Run Code Online (Sandbox Code Playgroud)
步骤结果
2-使用矩形轴获取角点
首先,我们想要扩展我们的轴(我们是 1px/单位大小)以获得宽度(对于 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),
]
}
Run Code Online (Sandbox Code Playgroud)
使用 Vector 的这 2 个新闻方法:
// Add(5)
// Add(Vector)
// Add({x, y})
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(5)
// Multiply(Vector)
// Multiply({x, 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,
})
}
Run Code Online (Sandbox Code Playgroud)
步骤结果
3-获取角点投影
对于一个矩形的每个角,获取另一个矩形在两个轴上的投影坐标。
只需将此函数添加到 Vector 类即可:
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,
})
}
Run Code Online (Sandbox Code Playgroud)
(特别感谢Mbo提供投影的解决方案。)
步骤结果
4- 选择投影上的外角
为了对(沿直角轴)所有投影点进行排序并获取外部投影点,我们可以:
get magnitude() {
return Math.sqrt(this.x * this.x + this.y * this.y);
}
Run Code Online (Sandbox Code Playgroud)
getSignedDistance = (rect, line, corner) => {
const projected = corner.Project(line);
const CP = projected.Minus(rect.center);
// Sign: Same directon of axis : true.
const sign = (CP.x * line.direction.x) + (CP.y * line.direction.y) > 0;
const signedDistance = CP.magnitude * (sign ? 1 : -1);
}
Run Code Online (Sandbox Code Playgroud)
然后使用简单的循环和最小/最大测试,我们可以找到 2 个外角。它们之间的线段是一个 Rect 在另一轴上的投影。
步骤结果
5-最终:所有投影都符合 rect 吗?
使用沿轴的简单一维测试我们可以知道它们是否撞击:
const isProjectionHit = (minSignedDistance < 0 && maxSignedDistance > 0
|| Math.abs(minSignedDistance) < rectHalfSize
|| Math.abs(maxSignedDistance) < rectHalfSize);
Run Code Online (Sandbox Code Playgroud)
完毕
测试所有 4 个投影将为您提供最终结果。=]!!
希望这个答案能够帮助尽可能多的人。如有任何意见,我们将不胜感激。