How to detect when rotated rectangles are colliding each other

只愿长相守 提交于 2020-07-03 13:00:47

问题


After saw this question many times and replied with an old (an not usable) code I decide to redo everything and post about it.

Rectangles are defined by:

  • center : x and y for his position (remember that 0;0 is TOP Left, so Y go down)
  • size: x and y for his size
  • angle for his rotation (in deg, 0 deg is following axis OX and turn clockwise)

The goal is to know if 2 rectangles are colliding or not.


回答1:


Will use Javascript in order to demo this (and also provide code) but I can be done on every language following the process.

Links

  • Final Demo on Codepen
  • GitHub repository

Concept

In order to achieve this we'll use corners projections on the other rectangle 2 axis (X and Y). The 2 rectangles are only colliding when the 4 projections on one rectangles hit the others:

  • Rect Blue corners on Rect Orange X axis
  • Rect Blue corners on Rect Orange Y axis
  • Rect Orange corners on Rect Blue X axis
  • Rect Orange corners on Rect Blue Y axis

Process

1- Find the rects axis

Start by creating 2 vectors for axis 0;0 (center of rect) to X (OX) and Y (OY) then rotate both of them in order to get aligned to rectangles axis.

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}),
  ];
}

Where Vector is a simple x,y object

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),
    });
  }
}

And Line represent a slop using 2 vectors:

  • origin: Vector for Start position
  • direction: Vector for unit direction
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});
  }
}

Step Result

2- Use Rect Axis to get corners

First want extend our axis (we are 1px unit size) in order to get the half of width (for X) and height (for Y) in order to be able by adding when (and inverse) to get all corners.

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),
  ]
}

Using this 2 news methods for Vector:

  // 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,
    })
  }

Step Result

3- Get corners projections

For every corners of a rectangle, get the projection coord on both axis of the other rectangle.

Simply by adding this function to 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,
    })
  }

(Special thank to Mbo for the solution to get projection.)

Step Result

4- Select externals corners on projections

In order to sort (along the rect axis) all the projected point and take the min and max projected points we can:

  • Create a vector to represent: Rect Center to Projected corner
  • Get the distance using the Vector Magnitude function.
  get magnitude() {
    return Math.sqrt(this.x * this.x + this.y * this.y);
  }
  • Use the dot product to know if the vector is facing the same direction of axis of inverse (where signed distance" is negative)
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);
}

Then using a simple loop and test of min/max we can find the 2 externals corners. The segment between them is the projection of a Rect on the other one axis.

Step result

5- Final: Do all projections hit rect ?

Using simple 1D test along the axis we can know if they hit or not:

const isProjectionHit = (minSignedDistance < 0 && maxSignedDistance > 0
        || Math.abs(minSignedDistance) < rectHalfSize
        || Math.abs(maxSignedDistance) < rectHalfSize);

Done

Testing all 4 projections will give you the final result. =] !!

Hope this answer will help as many people as possible. Any comments are appreciated.



来源:https://stackoverflow.com/questions/62028169/how-to-detect-when-rotated-rectangles-are-colliding-each-other

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!