6
\$\begingroup\$

This is my solution to find the coordinates of 2 overlapped rectangles implemented in JavaScript. Each rectangle is represented by 2 points, each with 2 (x,y) coordinates.

Cases of overlapped rectangles
Can this code be improved?

const maxOfX = (rec) => (Math.max(rec.x1, rec.x2));
const maxOfY = (rec) => (Math.max(rec.y1, rec.y2));
const minOfX = (rec) => (Math.min(rec.x1, rec.x2));
const minOfY = (rec) => (Math.min(rec.y1, rec.y2));
const comBinedRectangle = (rec1, rec2) => {
  let overlappedRec = {};
  const NolappingFromX = maxOfX(rec1) <= minOfX(rec2) || minOfX(rec1) >= maxOfX(rec2);
  const NolappingFromY = maxOfY(rec1) <= minOfY(rec2) || minOfY(rec1) >= maxOfY(rec2);
  if (!(NolappingFromX || NolappingFromY)) {
    overlappedRec.x1 = Math.max(minOfX(rec1), minOfX(rec2));
    overlappedRec.y1 = Math.max(minOfY(rec1), minOfY(rec2));
    overlappedRec.x2 = Math.min(maxOfX(rec1), maxOfX(rec2));
    overlappedRec.y2 = Math.min(maxOfY(rec1), maxOfY(rec2));
  }

  return overlappedRec;
}
const rectangle1 = { x1: 2, y1: 2, x2: 4, y2: 4 };
const rectangle2 = { x1: 3, y1: 3, x2: 6, y2: 2 };
console.log(comBinedRectangle(rectangle1, rectangle2));
// { x1: 3, y1: 2, x2: 4, y2: 3 }
\$\endgroup\$
3
  • \$\begingroup\$ Don't write, never present undocumented/uncommented code. Don't do (boolean expression) ? true : false. Try to limit line length. \$\endgroup\$
    – greybeard
    Jan 17, 2018 at 15:44
  • \$\begingroup\$ Are you talking about combination, intersection or even bounding rectangles? Why put 'overlapping' in the name when non-intersecting rectangles are equally valid input? \$\endgroup\$
    – le_m
    Jan 17, 2018 at 17:43
  • \$\begingroup\$ Why not create a Rectange class? I did something similar whilst working on a visualisation project and it helped make sense of the code e.g. rect1.intersects(rect2) \$\endgroup\$
    – James
    Jan 20, 2018 at 0:19

2 Answers 2

2
\$\begingroup\$

/**
 * Returns intersecting part of two rectangles
 * @param  {object}  r1 4 coordinates in form of {x1, y1, x2, y2} object
 * @param  {object}  r2 4 coordinates in form of {x1, y1, x2, y2} object
 * @return {boolean}    False if there's no intersecting part
 * @return {object}     4 coordinates in form of {x1, y1, x2, y2} object
 */
const getIntersectingRectangle = (r1, r2) => {  
  [r1, r2] = [r1, r2].map(r => {
    return {
      x: [r.x1, r.x2].sort((a,b) => a - b),
      y: [r.y1, r.y2].sort((a,b) => a - b)
    };
  });

  const noIntersect = r2.x[0] > r1.x[1] || r2.x[1] < r1.x[0] ||
                      r2.y[0] > r1.y[1] || r2.y[1] < r1.y[0];

  return noIntersect ? false : {
    x1: Math.max(r1.x[0], r2.x[0]), // _[0] is the lesser,
    y1: Math.max(r1.y[0], r2.y[0]), // _[1] is the greater
    x2: Math.min(r1.x[1], r2.x[1]),
    y2: Math.min(r1.y[1], r2.y[1])
  };
};

/*  ↓  DEMO  ↓  */

const rectangle1 = { x1: 2, y1: 2, x2: 4, y2: 4 };
const rectangle2 = { x1: 3, y1: 3, x2: 6, y2: 2 };

console.log(getIntersectingRectangle(rectangle1, rectangle2));
// { x1: 3, y1: 2, x2: 4, y2: 3 }

First, both rectangles get transformed into object with keys x and y and sorted arrays of two of corresponding them coordinates as values.

r1 = {
  x: [2, 4], // x1, x2
  y: [2, 4]  // y1, y2
}
r2 = {
  x: [3, 6], // x1, x2
  y: [2, 3]  // Y2, Y1 !
}

That's because for this job it is important to know which of xs and ys is lesser and which is greater. Rather than Math.max() and Math.min() a single .sort() can be used.

Alternative would be to declare that (x1, y1) is assumed to always be top-left corner, but as I see in rectangle2 in your question, that is apparently not always the case.

Now, noIntersect is negated

!(a.left > b.right || b.left > a.right || a.top > b.bottom || b.top > a.bottom);

which tests if in both axes sides from one end of one figure exceed opposite-direction end side of the other figure, e.g. if left side of figure a is more to the right than the right side of figure b.

At the end we return false if there is no intersection of our rectangles, or an object with coordinates if there is. Intersecting part will always span from:

the greater of the 2 lesser xs of both rectangles

to

the lesser of the 2 greater xs of both rectangles.

\$\endgroup\$
3
  • 2
    \$\begingroup\$ If I'm not wrong, the JS array.sort() functions sort values as strings, thus this will yield incorrect results (try: getIntersectingRectangle({x1: 420, y1: 5, x2: 509, y2: 36},{x1: 432, y1: 18, x2: 450, y2: 30}) ). You can fix the sort function by providing a comperator e.g. points.sort((a,b) => a - b) \$\endgroup\$
    – Ben Bracha
    Apr 22, 2019 at 9:36
  • \$\begingroup\$ @BenBracha Thank you so much for your comment. I encountered this exact bug and I would have never figured that out (working sometimes but sometimes not because number of digits was different). The answer should not be accepted with this bug. \$\endgroup\$
    – Markus
    May 30, 2020 at 21:13
  • \$\begingroup\$ @BenBracha Thanks, I missed that one! The answer was corrected. \$\endgroup\$
    – Przemek
    Feb 16, 2021 at 21:46
1
\$\begingroup\$
  • Document/comment
    I have no idea whether there are agreed standards for ECMAScript, e.g. JSDoc.
  • If I used what you called OverX I'd call it OverlapX and invert it.
    OverlapX = minOfX(rec1) <= maxOfX(rec2) && minOfX(rec2) <= maxOfX(rec1);
  • precomputing booleans the way of OverX and OverY precludes short-circuiting the evaluation of the combined no-overlap-condition.
  • One could use "nomalised" rects: minx/miny/maxx/maxy.
  • (I'd prefer plain function definitions over arrow functions if they are going to get a name, anyway.)
  • (There is bound to be a way to have, e.g. Math.min(values) operate on all x-coordinates of a shape, my ignorance thereof notwithstanding.)
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.