3
\$\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\$
  • \$\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 '18 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 '18 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 '18 at 0:19
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(), y: [r.y1, r.y2].sort()};
  });

  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\$
  • \$\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 at 9:36
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, privacy policy and cookie policy

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