3

I have an axis-aligned rectangle in a 2d coordinate system represented by the point on the lower left and the point on the upper right, as well as a point which might be inside or outside of the rectangle. I want to find the distance of the point to the nearest point of the rectangle, regardless of whether it is inside the rectangle or not. Of course I could just write a switch case with 9 different outcomes but I am hoping that there is a more elegant solution.
Also, I have found multiple solutions to this problem (like this one), but all of them would calculate the distance as 0 if the point is inside the box, which I don't want.

ddominnik
  • 93
  • 2
  • 9
  • "distance of the point to the nearest side of the rectangle" Is this exactly the same as the distance of the point to the nearest *point* of the rectangle? If not, what's the difference? – n. m. could be an AI Aug 24 '18 at 12:20
  • @n.m. that's exactly what I meant. I changed the title – ddominnik Aug 24 '18 at 12:22
  • A rectangle is made of 4 lines, so you can [find the absolute distance from the point to each line](https://en.wikipedia.org/wiki/Distance_from_a_point_to_a_line) and take the min value of the four distances. This will work regardless of whether the point is inside or outside the rectangle. – Matthew Pope Aug 24 '18 at 13:31
  • @MatthewPope a rectangle is made of four *line segments*, not of four lines. Your link does not discuss line segments at all. – n. m. could be an AI Aug 25 '18 at 06:45
  • I'm sorry—that was a total miss on my part. That's what I get for trying to answer questions late at night. You can get the distance from a point to a line segment by finding the projection of the point on the line which extends the line segment. Of the two endpoints and the projected point you just found, select the point that falls between the other two. That is the closest point on the line segment to the original point. Find the distance between the closest point on the line segment and the original point. Repeat for all line segments and take the min distance. – Matthew Pope Aug 26 '18 at 23:12

3 Answers3

5

My answer is slightly longer than the others, but it comes from a different perspective.

The key isn't if you're inside the rectangle, but if you're anywhere within the corridors defined by taking the sides of the rectangle and extending them infinitely (picture an infinite plus sign, centered on the rectangle).

If it's inside those corridors, then the closest distance is orthogonal to one of the sides.

If it's outside, then the closest distance is the distance to the nearest corner.

Your code could look like this:

nearest_distance(rectangle, point):
    d_top = abs(rectangle.top - point.y)
    d_bottom = abs(rectangle.bottom - point.y)
    corner_y = d_top < d_bottom ? rectangle.top : rectangle.bottom

    d_left = abs(rectangle.left - point.x)
    d_right = abs(rectangle.right - point.x)
    corner_x = d_left < d_right ? rectangle.left : rectangle.right

    d_cx = corner_x - point.x
    d_cy = corner_y - point.y
    d_corner = sqrt(d_cx*d_cx + d_cy*d_cy)

    return min(d_top, d_bottom, d_left, d_right, d_corner)

If you wanted to try to save a sqrt, you could check if you're inside the corridors vs. outside of them. In that case, you would rearrange it as follows:

nearest_distance(rectangle, point):
    d_top = abs(rectangle.top - point.y)
    d_bottom = abs(rectangle.bottom - point.y)
    d_left = abs(rectangle.left - point.x)
    d_right = abs(rectangle.right - point.x)

    r = rectangle # just to make the next line neater
    if r.left <= point.x <= r.right or r.bottom <= point.y <= r.top:
        return min(d_top, d_bottom, d_left, d_right)
    else:
        corner_y = d_top < d_bottom ? rectangle.top : rectangle.bottom
        corner_x = d_left < d_right ? rectangle.left : rectangle.right

        d_cx = corner_x - point.x
        d_cy = corner_y - point.y
        d_corner = sqrt(d_cx*d_cx + d_cy*d_cy)
        return d_corner
Scott Mermelstein
  • 15,174
  • 4
  • 48
  • 76
  • 1
    This was how I initially thought I'd solve this but when I looked at the answer OP linked I went for that instead. Good one! – Sani Huttunen Aug 24 '18 at 15:04
  • Great answer, thank you so much! Didn't really think about how to break the problem down differently than in the link – ddominnik Aug 27 '18 at 12:21
1

You could extend the linked solution of MultiRRomero and do some additional computations for points inside the rectangle.

For these points the closest point on the boundary of the rectangle has the same x or y coordinate as the point. So computing the distances to the lines is straight forward and the smallest will be the desired distance.

function distance(rect, p) {
  var dx = Math.max(rect.min.x - p.x, 0, p.x - rect.max.x);
  var dy = Math.max(rect.min.y - p.y, 0, p.y - rect.max.y);
  var distance = Math.sqrt(dx*dx + dy*dy)
  if (distance == 0) {
    distance = Math.min(p.x - rect.min.x, rect.max.x - p.x, p.y - rect.min.y, rect.max.y - p.y)
  }
  return distance
}

Edit: typos fixed

pH 74
  • 321
  • 2
  • 8
0

How about something like this? (first part "stolen" from MultiRRomeros answer)

function distance(rect, p) {
  // outside
  var dxo = Math.max(rect.min.x - p.x, 0, p.x - rect.max.x);
  var dyo = Math.max(rect.min.y - p.y, 0, p.y - rect.max.y);
  var hypothenuse = Math.sqrt(dxo*dxo + dyo*dyo);

  // inside
  var dxi = Math.min(rect.max.x - p.x, p.x - rect.min.x);
  var dyi = Math.min(rect.max.y - p.y, p.y - rect.min.y);

  return hypothenuse > 0 ? hypothenuse : Math.min(dxi, dyi);
}
Sani Huttunen
  • 23,620
  • 6
  • 72
  • 79
  • 1
    @ScottMermelstein: When the `point` is *inside* the rectangle the `hypothenuse` will be to `0` since `dxo` and `dxy` are also compared to `0`. So when the point is inside the rectangle, 0 will be the largest number. I.e. `rect.min.x - p.x` etc will be `0` or less. – Sani Huttunen Aug 24 '18 at 14:29