如何检测旋转的矩形何时相互碰撞
How to detect when rotated rectangles are colliding each other
在多次看到这个问题并用旧的(不可用的)代码回答后,我决定重做所有事情并 post 关于它。
矩形 定义为:
center
: x
和 y
代表他的位置(记住 0;0 是 TOP Left,所以 Y 往下)
size
:x
和 y
适合他的尺码
angle
为他的旋转(以度为单位,0 度是跟随轴 OX 并顺时针旋转)
目标 是了解 2 个矩形是否发生碰撞。
将使用 Javascript 来演示它(并提供代码),但我可以按照流程在每种语言上完成。
链接
概念
为了实现这一点,我们将在另一个矩形 2 轴(X 和 Y)上使用角投影。
只有当一个矩形上的 4 个投影碰到其他矩形时,2 个矩形才会发生碰撞:
- 矩形橙色 X 轴上的蓝色角
- 矩形橙色 Y 轴上的蓝色角
- 矩形蓝色 X 轴上的橙色角
- 矩形蓝色 Y 轴上的橙色角
进程
1- 求直角轴
首先为轴 0 创建 2 个向量;0(矩形的中心)到 X (OX) 和 Y (OY),然后旋转它们以与矩形轴对齐。
Wikipedia about rotate a 2D vector
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}),
];
}
其中 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),
});
}
}
Line 使用 2 个向量表示斜率:
- origin:开始位置的矢量
- 方向:单位方向向量
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-使用Rect Axis得到角
首先要扩展我们的轴(我们是 1px 单位大小)以获得宽度(对于 X)和高度(对于 Y)的一半,以便能够通过添加 when(和逆)来获得所有角落.
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),
]
}
对 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,
})
}
步骤结果
3-获取角投影
对于一个矩形的每个角,获取另一个矩形在两个轴上的投影坐标。
只需将此函数添加到 Vector class:
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- Select 投影上的外角
为了(沿直轴)对所有投影点进行排序并取最小和最大投影点,我们可以:
- 创建一个向量来表示:Rect Center to Projected corner
- 使用矢量幅度函数获取距离。
get magnitude() {
return Math.sqrt(this.x * this.x + this.y * this.y);
}
- 使用dot product知道矢量是否面向反轴的相同方向(其中符号距离"为负)
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);
}
然后使用一个简单的循环和测试 min/max 我们可以找到 2 个外角。它们之间的线段是 Rect 在另一个轴上的投影。
步骤结果
5- 决赛:所有投影都正确吗?
使用沿轴的简单一维测试我们可以知道它们是否命中:
const isProjectionHit = (minSignedDistance < 0 && maxSignedDistance > 0
|| Math.abs(minSignedDistance) < rectHalfSize
|| Math.abs(maxSignedDistance) < rectHalfSize);
完成
测试所有 4 个投影将为您提供最终结果。 =] !!
希望这个回答能帮助到尽可能多的人。任何评论表示赞赏。
在多次看到这个问题并用旧的(不可用的)代码回答后,我决定重做所有事情并 post 关于它。
矩形 定义为:
center
:x
和y
代表他的位置(记住 0;0 是 TOP Left,所以 Y 往下)size
:x
和y
适合他的尺码angle
为他的旋转(以度为单位,0 度是跟随轴 OX 并顺时针旋转)
目标 是了解 2 个矩形是否发生碰撞。
将使用 Javascript 来演示它(并提供代码),但我可以按照流程在每种语言上完成。
链接
概念
为了实现这一点,我们将在另一个矩形 2 轴(X 和 Y)上使用角投影。 只有当一个矩形上的 4 个投影碰到其他矩形时,2 个矩形才会发生碰撞:
- 矩形橙色 X 轴上的蓝色角
- 矩形橙色 Y 轴上的蓝色角
- 矩形蓝色 X 轴上的橙色角
- 矩形蓝色 Y 轴上的橙色角
进程
1- 求直角轴
首先为轴 0 创建 2 个向量;0(矩形的中心)到 X (OX) 和 Y (OY),然后旋转它们以与矩形轴对齐。
Wikipedia about rotate a 2D vector
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}),
];
}
其中 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),
});
}
}
Line 使用 2 个向量表示斜率:
- origin:开始位置的矢量
- 方向:单位方向向量
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-使用Rect Axis得到角
首先要扩展我们的轴(我们是 1px 单位大小)以获得宽度(对于 X)和高度(对于 Y)的一半,以便能够通过添加 when(和逆)来获得所有角落.
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),
]
}
对 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,
})
}
步骤结果
3-获取角投影
对于一个矩形的每个角,获取另一个矩形在两个轴上的投影坐标。
只需将此函数添加到 Vector class:
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- Select 投影上的外角
为了(沿直轴)对所有投影点进行排序并取最小和最大投影点,我们可以:
- 创建一个向量来表示:Rect Center to Projected corner
- 使用矢量幅度函数获取距离。
get magnitude() {
return Math.sqrt(this.x * this.x + this.y * this.y);
}
- 使用dot product知道矢量是否面向反轴的相同方向(其中符号距离"为负)
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);
}
然后使用一个简单的循环和测试 min/max 我们可以找到 2 个外角。它们之间的线段是 Rect 在另一个轴上的投影。
步骤结果
5- 决赛:所有投影都正确吗?
使用沿轴的简单一维测试我们可以知道它们是否命中:
const isProjectionHit = (minSignedDistance < 0 && maxSignedDistance > 0
|| Math.abs(minSignedDistance) < rectHalfSize
|| Math.abs(maxSignedDistance) < rectHalfSize);
完成
测试所有 4 个投影将为您提供最终结果。 =] !!
希望这个回答能帮助到尽可能多的人。任何评论表示赞赏。