找到矩形和圆形之间的交点

Find the intersection points between a rectangle and a circle

有一个矩形和一个圆,我想有一个函数可以 return 我得到它们碰撞点的坐标 像这样

let myrect = { x: 100, y: 100, w: 100, h: 50 };
let mycircle = { x: 156, y: 156, r: 100 };

function detectCoords(rect, circle) {
    //do something
    return [{ x: 5, y: 2}, { x: 3, y: 7}] //example
}

这里有一个简单的方法来检查矩形的坐标是否在圆内。只需使用简单循环递增值即可。

let rect = { x: 40, y: 100, w: 100, h: 50 }
let mycircle = { x: 156, y: 156, r: 100 }

function isInside(circle_x, circle_y, rad, x, y) {
  //formula x^2+y^2=r^2
  if (
    (x - circle_x) * (x - circle_x) + (y - circle_y) * (y - circle_y) <=
    rad * rad
  )
    return true
  else return false
}
function getIntersectionPoint(circle, rect) {
  let coor = []
  let notFullyInside = false
  //for top
  for (let index = rect.x; index < rect.x + rect.w; index++) {
    if (isInside(circle.x, circle.y, circle.r, index, rect.y)) {
      if (notFullyInside) {
        coor.push({ x: index, y: rect.y })
        break
      }
    } else {
      notFullyInside = true
    }
  }
  notFullyInside = false
  //for left
  for (let index = rect.y; index < rect.y + rect.h; index++) {
    if (isInside(circle.x, circle.y, circle.r, rect.x, index)) {
      if (notFullyInside) {
        coor.push({ x: rect.x, y: index })
        break
      }
    } else {
      notFullyInside = true
    }
  }
  notFullyInside = false
  //for right
  for (let index = rect.y; index < rect.y + rect.h; index++) {
    if (isInside(circle.x, circle.y, circle.r, rect.x + rect.w, index)) {
      if (notFullyInside) {
        coor.push({ x: rect.x + rect.w, y: index })
        break
      }
    } else {
      notFullyInside = true
    }
  }
  notFullyInside = false
  //for bottom
  for (let index = rect.x; index < rect.x + rect.w; index++) {
    if (isInside(circle.x, circle.y, circle.r, index, rect.y + rect.h)) {
      if (notFullyInside) {
        coor.push({ x: index, y: rect.y + rect.h })
        break
      }
    } else {
      notFullyInside = true
    }
  }
  return coor
}

console.log(getIntersectionPoint(mycircle, rect))

基于。唯一需要的代码是 intersections 函数(这是您的 detectCoords 函数)。如果您有任何疑问,请发表评论:-)

function intersections (rect, circle) {
  var y_top     = rect.y - rect.h / 2;
  var x_right   = rect.x + rect.w / 2;
  var y_bottom  = rect.y + rect.h / 2;
  var x_left    = rect.x - rect.w / 2;
  return [
    // absolute coordinates
    // of all the `rect` edges
    /* 0 even */ y_top,
    /* 1 odd  */ x_right,
    /* 2 even */ y_bottom,
    /* 3 odd  */ x_left
  ].map(function (x_or_y, i) {
    // relative coordinate
    // of one `rect` edge
    return x_or_y - (
      // `i & 1` is 0 or 1
      circle["yx"[i & 1]]
    );
  }).map(function (x_or_y, i) {
    // edge out of circle
    if (Math.abs(x_or_y) > circle.r) {
      return [];
    } 
    // edge and circle intersect
    else {
      let y_or_x, x1, y1, x2, y2;
      y_or_x = Math.sqrt(
        circle.r ** 2 - x_or_y ** 2
      );
      i = i & 1; // 0 or 1
      x1 = [y_or_x, x_or_y][i];
      y1 = [x_or_y, y_or_x][i];
      x2 = x1 * (i ? +1 : -1);
      y2 = y1 * (i ? -1 : +1);
      // two elligible points
      // with absolute coordinates
      return [{
        x : circle.x + x1,
        y : circle.y + y1
      }, {
        x : circle.x + x2,
        y : circle.y + y2
      }];
    }
  }).reduce(function (acc, xys, i) {
    var k, min, max;
    i = i & 1; // 0 or 1
    k = "xy"[i];
    min = [x_left, y_top][i];
    max = [x_right, y_bottom][i];
    return acc.concat(xys.filter(function (xy) {
      // `xy` is on the edge ? yes : no
      return xy[k] >= min && xy[k] <= max;
    }));
  }, []);
}

// optional code

onload = function () {
  var canvasEl = getEl("canvas");
  var ctx = canvasEl.getContext("2d");
  canvasEl.width = 400;
  canvasEl.height = 300;
  draw(
    ctx,
    readRect(),
    readCircle()
  );
  onSubmit("form", function (ev) {
    ev.preventDefault();
    ctx.clearRect(
      0, 0,
      canvasEl.width,
      canvasEl.height
    );
    draw(
      ctx,
      readRect(),
      readCircle()
    );
  });
}

function readRect () {
  var x, y, w, h;
  var rectXyEl = getEl("rect-xy");
  var rectWhEl = getEl("rect-wh");
  [x, y] = readPair(rectXyEl);
  [w, h] = readPair(rectWhEl);
  return { x : x, y : y, w : w, h : h };
}

function readCircle () {
  var x, y, r;
  var circleXyEl = getEl("circle-xy");
  var circleREl = getEl("circle-r");
  [x, y] = readPair(circleXyEl);
  r = parseInt(circleREl.value, 10);
  return { x : x, y : y, r : r };
}

function readPair (el) {
  return el.value.split(" ").map(
    (x) => parseInt(x, 10)
  );
}

function draw (ctx, rect, circle) {
  drawRect(ctx, rect);
  drawCircle(ctx, circle);
  drawIntersections(ctx, rect, circle);
}

function drawRect (ctx, rect) {
  ctx.beginPath();
  ctx.rect(
    rect.x - rect.w / 2,
    rect.y - rect.h / 2,
    rect.w, rect.h
  );
  ctx.stroke();
}

function drawCircle (ctx, circle) {
  ctx.beginPath();
  ctx.arc(
    circle.x, circle.y,
    circle.r,
    0, 2 * Math.PI
  );
  ctx.stroke();
}

function drawIntersections (ctx, rect, circle) {
  for (let xy of intersections(rect, circle)) {
    ctx.beginPath();
    ctx.arc(xy.x, xy.y, 3, 0, 2 * Math.PI, true);
    ctx.stroke();
  }
}

function onSubmit (id, f) {
  getEl(id).addEventListener("submit", f);
}

function getEl (id) {
  return document.getElementById(id);
}
body {
  margin: .5em;
  background: #ddd;
}
input[type=text] {
  width: 60px;
}
input[type=submit] {
  margin-top: .5em;
}
.column {
  float: left;
}
.column:first-child {
  background: white;
  margin-right: .5em;
  padding: .5em;
  width: 90px;
}
<div class="column">
  <form id="form">
    rect x y <input
      type="text"
      id="rect-xy"
      value="100 100"
    >
    rect w h <input
      type="text"
      id="rect-wh"
      value="130 130"
    >
    circle x y <input
      type="text"
      id="circle-xy"
      value="100 100"
    >
    circle r <input
      type="text"
      id="circle-r"
      value="75"
    >
    <input type="submit">
  </form>
</div>
<div class="column">
  <canvas id="canvas" style="background:white"></canvas>
</div>

表达式 n & 1 是一个 bitwise and。它选择基数 2 中 n 的“最右边”位。

> | 0 & 1 // 0b00
< | 0
> | 1 & 1 // 0b01
< | 1
> | 2 & 1 // 0b10
< | 0
> | 3 & 1 // 0b11
< | 1

您可以使用 n & 1 来检查一个数字是否为奇数。

> | 4 & 1 ? "odd" : "even"
< | "even"

高效实施指南:

WLOG 圆心是原点(如果不是你可以平移所有点)。然后假设在纵坐标 Y 和横坐标 X0 < X1 处的水平边缘。

如果 Y < -R 或 Y > R,则没有交集。

否则,检查 X0² > R² - Y² 和 X1² > R² - Y²。如果两者都为假,则该段完全位于内部。一假一真,只有一个交集。如果两个为真,则有两个交集。

交点的纵坐标为Y,所以横坐标为±√(R² - Y²)。根据以上inside/outside条件取负and/or正号。 (true/false 是 -,false/true 是 +,true/true 都是。)

对四个边重复上述步骤。 (您可以将水平边的条件重复用于垂直边。)