0

having a rectangle and a circle, I would like to have a function that would return me the coordinates of the points where they collide something like this

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
}

enter image description here

MrCano369x
  • 328
  • 3
  • 6
  • 2
    The intersection of a circle and a rectangle must be broken down into the intersection of the circle and a line, which is repeated for each of the four sides of the rectangle. Then the solutions for the intersection of the line must be checked to see if it falls within the extents of the rectangle. – Wyck Jan 12 '22 at 20:55
  • 2
    What @Wyck said. Decent-looking code for circle-segment intercept can be found in e.g. this answer: https://stackoverflow.com/a/37225895/51685 – AKX Jan 12 '22 at 20:57
  • 1
    Or here https://stackoverflow.com/a/1088058/294949. Do these four times and you're done. – danh Jan 12 '22 at 20:57
  • 3
    Note that the solution for the intersection of a _circle centered at the origin_ and an _axis-aligned line_ is simpler than the solution for the intersection of an arbitrary circle an an arbitrary line. So assuming your rectangle is axis-aligned, then take advantage of the simple equations of your line (which might be something like y=5, (relative to the center of the circle) in which case it's straightforward to find the intersection with the circle by solving for x in the equation `x*x + y*y = r*r`. Giving you `x = ±sqrt(r*r-5*5)` – Wyck Jan 12 '22 at 21:03
  • Notice that there can be up to eight intersection points. –  Jan 13 '22 at 15:55
  • @Wyck's comment is the way to go however i just would like to know your real problem. What exactly you want to do next by finding the intersection points? Perhaps you don't even need them. – Redu Jan 13 '22 at 16:56

3 Answers3

2

Based on Wyck's comment. The only required code is the intersections function (which is your detectCoords function). If you have questions, drop a comment :-)

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>

The expression n & 1 is a bitwise and. It picks the "rightmost" bit of n in base 2.

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

You can use n & 1 to check whether a number is odd.

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

Here is a simple method that checks coordinate of rectangle exists within the circle or not. just by incrementing the values using simple loop.

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))
arn48
  • 310
  • 4
  • 12
0

Guidelines for an efficient implementation:

WLOG the center of the circle is the origin (if not you can translate all points). Then assume an horizontal edge at ordinate Y and abscissas X0 < X1.

If Y < -R or Y > R, there is no intersection.

Else, check X0² > R² - Y² and X1² > R² - Y². If both false, the segment is wholly inside. If one false and one true, there is a single intersection. If two true, there are two intersections.

The intersections have ordinate Y, so abscissa ±√(R² - Y²). Take the negative and/or positive sign depending on the above inside/outside conditions. (true/false is -, false/true is +, true/true is both.)

Repeat for the four sides. (You can reuse the conditions on the horizontal sides for the vertical ones.)