圆形旋转矩形碰撞检测

3
我正在尝试按照这个链接(http://www.migapro.com/circle-and-rotated-rectangle-collision-detection/)实现旋转矩形和圆形之间的碰撞检测。
我在jsfiddle中添加了代码,链接在这里(http://jsfiddle.net/Z6KSX/2/)。
我错过了什么吗?

function check_coll ( circle_x,circle_y, rect_x, rect_y, rect_width, rect_height, rect_angle) 
   {

    // Rotate circle's center point back
    var rect_centerX = rect_x /2 ;
    var rect_centerY = rect_y /2 ;

    var cx = (Math.cos(rect_angle) * (circle_x - rect_centerX)) - (Math.sin(rect_angle) * (circle_y - rect_centerY)) + rect_centerX;
    var cy = (Math.sin(rect_angle) * (circle_x - rect_centerX)) + (Math.cos(rect_angle) * (circle_y - rect_centerY)) + rect_centerY;

    // Closest point
    var x, y;

    // Find the unrotated closest x point from center of unrotated circle
    if (cx < rect_x) {
        x = rect_x;
    }
    else if (cx > rect_x + rect_width){
        x = rect_x + rect_width;
    }
    else{
        x = cx;
     }

    // Find the unrotated closest y point from center of unrotated circle
    if (cy < rect_y){
        y = rect_y;
    }
    else if (cy > rect_y + rect_height) {
        y = rect_y + rect_height;
    }
    else {
        y = cy;
     }
    // Determine collision
    var collision = false;

    var c_radius = 5;
    var distance = findDistance(cx, cy, x, y);

    if (distance < c_radius) {
        collision = true; // Collision
    }
    else {
        collision = false;
    }
     return collision;
}


function findDistance (x1, y1, x2, y2) {
       var a = Math.abs(x1 - x2);
       var b = Math.abs(y1 - y2);

       var c = Math.sqrt((a * a) + (b * b));
       return c;
}

4个回答

2

哈哈,我觉得这很有趣,因为我最近花了很长时间走了很多弯路,终于找到了解决方法:

1.) 将圆心点按照矩形被旋转的负角度旋转。现在该点与矩形对齐(在矩形相对坐标空间中)。

2.) 解决圆和AABB相遇的问题。我解决它的方式是给我一个离圆心最近的矩形上的点。

3.) 将第二步得到的结果点按照正角度旋转。继续像往常一样解决(检查该点与圆心之间的距离是否在圆的半径内)。

从您的代码快速扫描,看起来可能是在做同样的事情,但是缺少了最后一步?我建议在矩形上的第二步得到的点上进行绘制,以便精确定位并帮助调试。


我无法理解,请您能否提供更多细节? - user94437
从你的代码中看出来有点困难,但我的意思是圆形与OOBB与圆形与AABB相同,只是需要将圆心旋转负矩形旋转量,然后在碰撞测试之后(测试方式就像圆形与AABB一样),将结果点旋转矩形的旋转量,然后使用该点检查是否在圆内。 - mitim
这是最好的方法吗?旋转向量并将其恢复看起来对我来说有些繁重。线性代数中有使用矩阵的方法。嗯,你的答案可能是这些方法的精髓。但是使用角度让我有点害怕。 - FLAW

0

我已经解决了这个问题。代码中的问题是,我使用了错误的半径,并且忽略了rect_x和rect_y的中心点。

   var rect_centerX = rect_x + (rect_width / 2);
   var rect_centerY = rect_y + (rect_height /2);

处理画布旋转时,我们需要将平移值添加到用于创建矩形的相应x和y值中。


0

我也在我的项目中使用这段代码,它很有效。唯一需要做的事情是使用-angle代替angle

这是我的代码链接

const canvas = document.getElementById("canvas");
const ctx = canvas.getContext("2d");
const rectX = 100;
const rectY = 100;
const rectWidth = 200;
const rectHeight = 100;
const circleRadius = 2;
const rectMidPointX = rectX + rectWidth / 2;
const rectMidPointY = rectY + rectHeight / 2;
const angle = Math.PI / 4;
let circleX;
let circleY;


canvas.addEventListener('mousemove', (e) => {
  circleX = e.clientX;
  circleY = e.clientY;

  ctx.save();
  ctx.beginPath();
  ctx.fillStyle = '#fff';
  ctx.arc(circleX, circleY, circleRadius, 0, 2 * Math.PI);
  ctx.fill();
  ctx.stroke();
  ctx.restore();
    calculateIntersection();
})

ctx.save();

//ctx.fillRect(100, 100, 100, 100);
ctx.strokeStyle = 'black';
ctx.translate(rectMidPointX, rectMidPointY);
ctx.rotate(angle);
ctx.translate(-rectMidPointX, -rectMidPointY);
ctx.strokeRect(rectX, rectY, rectWidth, rectHeight);
ctx.restore();

// Determine collision
let collision = false;

const findDistance = (fromX, fromY, toX, toY) => {
  const a = Math.abs(fromX - toX);
  const b = Math.abs(fromY - toY);

  return Math.sqrt((a * a) + (b * b));
};


function calculateIntersection() {
  // Rotate circle's center point back
  const unrotatedCircleX = Math.cos(-angle) * (circleX - rectMidPointX) -
    Math.sin(-angle) * (circleY - rectMidPointY) + rectMidPointX;
  const unrotatedCircleY = Math.sin(-angle) * (circleX - rectMidPointX) +
    Math.cos(-angle) * (circleY - rectMidPointY) + rectMidPointY;

  // Closest point in the rectangle to the center of circle rotated backwards(unrotated)
  let closestX, closestY;

  // Find the unrotated closest x point from center of unrotated circle
  if (unrotatedCircleX < rectX)
    closestX = rectX;
  else if (unrotatedCircleX > rectX + rectWidth)
    closestX = rectX + rectWidth;
  else
    closestX = unrotatedCircleX;

  // Find the unrotated closest y point from center of unrotated circle
  if (unrotatedCircleY < rectY)
    closestY = rectY;
  else if (unrotatedCircleY > rectY + rectHeight)
    closestY = rectY + rectHeight;
  else
    closestY = unrotatedCircleY;

  const distance = findDistance(unrotatedCircleX, unrotatedCircleY, closestX, closestY);
  if (distance < circleRadius)
    collision = true; // Collision
  else
    collision = false;

  console.log('collision', collision);
}
<canvas id="canvas" width="400px" height="400px" />


0

好的,我知道这个问题很旧了,但是我想发布我的解决方案,因为我已经苦苦寻找了几天一个好的解决方案。以下是我的解决方案:

const canvas = document.querySelector("canvas");
const ctx = canvas.getContext("2d");

const rect = {
  x: 75,
  y: 75,
  centerX: 100,
  centerY: 100,
  width: 50,
  height: 50,
  color: "#9cffc6",
  rotation: 45
}
const circle = {
  x: 0,
  y: 0,
  radius: 15,
  color: "#000000"
}

// gets rotated position of a point
function getRotatedPos(origin, point, angle) {
  const rotatedX = (Math.cos(angle) * (point.x - origin.x)) + (Math.sin(angle) * (point.y - origin.y)) + origin.x;
  const rotatedY = (Math.cos(angle) * (point.y - origin.y)) - (Math.sin(angle) * (point.x - origin.x)) + origin.y;

  return {
    x: rotatedX,
    y: rotatedY
  }
}

// converts degrees to radians
function degreesToRadians(degrees) {
  return degrees * Math.PI / 180;
}

// checks collision between the circle and rect
function checkCollision() {
  const radians = degreesToRadians(rect.rotation); // get the rotation of the rectangle in radians
  const origin = {
    x: rect.centerX,
    y: rect.centerY
  }

  const newCirclePos = getRotatedPos(origin, {x: circle.x, y: circle.y}, radians); // rotate the circle to the same axis as the rectangle

  let closestX;
  let closestY;

  // gets the closest x position on the rectangle to the circle
  if (newCirclePos.x < rect.x) closestX  = rect.x;
  else if (newCirclePos.x > rect.x + rect.width) closestX = rect.x + rect.width;
  else closestX = newCirclePos.x;

  // gets the closest y position on the rectangle to the circle
  if (newCirclePos.y < rect.y) closestY  = rect.y;
  else if (newCirclePos.y > rect.y + rect.height) closestY = rect.y + rect.height;
  else closestY = newCirclePos.y;

  // gets the distance of the closest point on the rectangle to the circle
  const distX = Math.abs(newCirclePos.x - closestX);
  const distY = Math.abs(newCirclePos.y - closestY);
  const distance = Math.sqrt((distX ** 2) + (distY ** 2));

  // if the distance is less than the circle's radius, there is a collision
  if (distance < circle.radius) return true;

  return false;
}

function update() {
  ctx.clearRect(0, 0, canvas.width, canvas.height); // clears the canvas
  
  // rotate the canvas
  ctx.save();
  ctx.translate(rect.x + rect.width / 2, rect.y + rect.height / 2);
  ctx.rotate(rect.rotation * Math.PI / 180);
  
  // if there is a collision, only the outline of the rect is drawn
  if (checkCollision()) {
    ctx.strokeStyle = rect.color;
    ctx.strokeRect(-rect.width / 2, -rect.height / 2, rect.width, rect.height);
  }
  else {
    ctx.fillStyle = rect.color;
    ctx.fillRect(-rect.width / 2, -rect.height / 2, rect.width, rect.height);
  }
  
  // restore the canvas to its original rotation
  ctx.restore();
  
  // draw the circle
  ctx.beginPath();
  ctx.arc(circle.x, circle.y, circle.radius, 0, Math.PI * 2);
  ctx.fillStyle = circle.color;
  ctx.fill();
  
  requestAnimationFrame(update);
}

update();

// sets the circle's position to the mouse position
canvas.addEventListener("mousemove", function (event) {
  const rect = canvas.getBoundingClientRect();
  const scaleX = canvas.width / rect.width;
  const scaleY = canvas.height / rect.height;

  const mouseX = (event.clientX - rect.left) * scaleX;
  const mouseY = (event.clientY - rect.top) * scaleY;
  
  circle.x = mouseX;
  circle.y = mouseY;
}, false);
* {
  margin: 0px;
  padding: 0px;
}

canvas {
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  position: absolute;
}
<canvas width="200px" height="200px"></canvas>

编辑: 我更新了我的答案,提供了一个更简单的解决方案并给出了更好的解释。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接