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

How to detect when rotated rectangles are colliding each other

在多次看到这个问题并用旧的(不可用的)代码回答后,我决定重做所有事情并 post 关于它。

矩形 定义为:

目标 是了解 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 个投影将为您提供最终结果。 =] !!

希望这个回答能帮助到尽可能多的人。任何评论表示赞赏。