29
\$\begingroup\$

I have a 2D rectangle with x, y position, height and width, and a randomly positioned point nearby.

Is there a way to check if this point might collide with the rectangle if it is closer than a certain distance? Imagine an invisible radius outside of that point colliding with said rectangle. I have problems with this simply because it is not a square!

\$\endgroup\$
26
\$\begingroup\$

If (x,y) is the centre of the rectangle, the squared distance from a point (px,py) to the rectangle's border can be computed this way:

dx = max(abs(px - x) - width / 2, 0);
dy = max(abs(py - y) - height / 2, 0);
return dx * dx + dy * dy;

If that squared distance is zero, it means the point touches or is inside the rectangle.

\$\endgroup\$
  • 6
    \$\begingroup\$ For anyone else who is wondering, (x, y) is the center of the rectangle, not the corner \$\endgroup\$ – Greg Rozmarynowycz Oct 13 '14 at 21:12
  • 2
    \$\begingroup\$ Sorry for the old comment, but does this equation assume that the rectangle is axis-aligned? \$\endgroup\$ – BitNinja Oct 25 '14 at 1:13
  • 1
    \$\begingroup\$ @BitNinja yes, that’s what the question assumes. If it’s not axis-aligned, the fastest/simplest algorithm will depend on how the rectangle information is stored. \$\endgroup\$ – sam hocevar Oct 25 '14 at 13:47
  • \$\begingroup\$ say, point is (4:4), rectangle is at (5:5) with width/height (5:5). Your code would claim that the point touches or is inside the rectangle, but it's obviously outside \$\endgroup\$ – LRN Aug 3 '17 at 12:54
  • \$\begingroup\$ @LRN a rectangle centered at (5:5) with width/height (5:5) spans from (2.5:2.5) to (7.5:7.5). The point (4:4) is inside that rectangle. \$\endgroup\$ – sam hocevar Aug 3 '17 at 14:17
11
\$\begingroup\$

I assume your rectangle is Axis-Aligned.

You just have to "clamp" the point into the rectangle and then compute the distance from the clamped point.

Point = (px, py), Rectangle = (rx, ry, rwidth, rheight) // (top left corner, dimensions)

function pointRectDist (px, py, rx, ry, rwidth, rheight)
{
    var cx = Math.max(Math.min(px, rx+rwidth ), rx);
    var cy = Math.max(Math.min(py, ry+rheight), ry);
    return Math.sqrt( (px-cx)*(px-cx) + (py-cy)*(py-cy) );
}
\$\endgroup\$
3
\$\begingroup\$

You must use circle-rectangle collisions for this. There's a similar question on Stack Overflow.

Your circle's center would be the point in question, and the radius would be the distance you want to check.

\$\endgroup\$
3
\$\begingroup\$

If you're trying to figure out the distance from a point to a rectangle's edge, working with each of the nine regions created by the rectangle might be the fastest way:

function pointRectangleDistance(x, y, x1, y1, x2, y2) {
    var dx, dy;
    if (x < x1) {
        dx = x1 - x;
        if (y < y1) {
            dy = y1 - y;
            return Math.sqrt(dx * dx + dy * dy);
        }
        else if (y > y2) {
            dy = y - y2;
            return Math.sqrt(dx * dx + dy * dy);
        }
        else {
            return dx;
        }
    }
    else if (x > x2) {
        dx = x - x2;
        if (y < y1) {
            dy = y1 - y;
            return Math.sqrt(dx * dx + dy * dy);
        }
        else if (y > y2) {
            dy = y - y2;
            return Math.sqrt(dx * dx + dy * dy);
        }
        else {
            return dx;
        }
    }
    else {
        if (y < y1) {
            return y1 - y;
        }
        else if (y > y2) {
            return y - y2;
        }
        else {
            return 0.0; // inside the rectangle or on the edge
        }
    }
}
\$\endgroup\$
2
\$\begingroup\$

[Modified answer based on comments]

If you want to see if the point is within say 10 units if the grey rectangle in the image below, you check if the point is in any one of

  1. red rectangle
  2. Blue rectangle
  3. any one of the green circles (radius 10)

enter image description here

inside=false;

bluerect.x=oldrect.x-10;
bluerect.y=oldrect.y;
bluerect.width=oldrect.width;
bluerect.height=oldrect.height+20;

if(  point.x >=bluerect && point.x <=redrect.x+bluerect.width &&
     point.y >=bluerect && point.y <=redrect.y+bluerect.height){
         //now point is side the blue rectangle
         inside=true;
}

redrect.x=oldrect.x;
redrect.y=oldrect.y-10;
redrect.width=oldrect.width+20;
redrect.height=oldrect.height;

if(  point.x >=redrect&& point.x <=redrect.x+redrect.width &&
     point.y >=redrect&& point.y <=redrect.y+redrect.height){
         //now point is side the redrectangle
         inside=true;
}


d1= distance(point, new point(oldrect.x, oldrect.y)) //calculate distance between point and (oldrect.x, oldrect.y)
d2= distance(point, new point(oldrect.x+10, oldrect.y))
d3= distance(point, new point(oldrect.x, oldrect.y+10))
d4= distance(point, new point(oldrect.x+10, oldrect.y+10))
if (d1 < 10 || d2 <10 || d3 < 10 || d4 <10){
    inside=true;
}

//inside is now true if the point is within 10 units of rectangle

This approach is a little inelegant. A similar method which avoids having to test all 4 corners by using rectangle symmetry is documented here on stackoverflow

\$\endgroup\$
  • \$\begingroup\$ In the diagonal direction this will give a false positive to points that are eg. 11 units away. \$\endgroup\$ – Eric B Nov 26 '12 at 15:56
  • \$\begingroup\$ The updated picture is blatantly wrong, in fact it actually illustrates the error case and makes it appear correct. That green point could easily be more than 10 units away and be inside that outer rectangle. \$\endgroup\$ – Eric B Nov 26 '12 at 16:15
  • \$\begingroup\$ Hey @EricB, I've fixed the error you pointed out, how about undoing your downvote? \$\endgroup\$ – Ken Nov 26 '12 at 17:37
  • \$\begingroup\$ Your answer will no longer give strictly incorrect results, so I removed the downvote, but it is also not the best way at all. Why not just test to see if the center is within the rectangle, and if the four line segments intersect the circle? The construction of these new rectangles and circles is just not necessary. Your answer also does not provide the actual distance from the point to the rectangle. \$\endgroup\$ – Eric B Nov 26 '12 at 17:52
  • \$\begingroup\$ This answer is honestly awful. 12 additions, 4 object constructions, 12 tests, 4 square roots for a task that actually requires 3 lines of code? \$\endgroup\$ – sam hocevar Nov 28 '12 at 12:12
-2
\$\begingroup\$

You could use something like this: enter image description here

\$\endgroup\$
  • \$\begingroup\$ This method seems unnecessarily complicated. Finding x1 and y1 is not necessary to solve this problem. \$\endgroup\$ – Eric B Nov 26 '12 at 15:59
  • \$\begingroup\$ In fact, this doesn't even satisfy the requirement of finding a collision within a given distance. It's just a bad way of detecting if the point is within the rectangle. \$\endgroup\$ – Eric B Nov 26 '12 at 16:06
  • \$\begingroup\$ A measure of distance is already implicitly there. if (d2<10*10) {/*within 10 units of measure*/} \$\endgroup\$ – AlexanderBrevig Nov 27 '12 at 12:04

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.