如何检测旋转的矩形何时相互碰撞

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 个矩形才会发生碰撞:

  • 矩形橙色 X 轴上的矩形蓝色角
  • 矩形橙色 Y 轴上的矩形蓝色角
  • 矩形蓝色 X 轴上的矩形橙色角
  • 矩形蓝色 Y 轴上的矩形橙色角

3个概念预览

过程

1-找到矩形轴

首先为轴 0;0(矩形中心)创建 2 个向量到 X (OX) 和 Y (OY),然后旋转它们以与矩形轴对齐。

关于旋转 2D 矢量的维基百科

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 个向量表示斜率:

  • 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});
  }
}
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 个投影将为您提供最终结果。=]!!

3个概念预览

希望这个答案能够帮助尽可能多的人。如有任何意见,我们将不胜感激。