DISABLE ADBLOCK

ADBlock is blocking some content on the site

ADBlock errore

Determining if a set of points are inside or outside a square

StackOverflow https://stackoverflow.com/questions/13217224
  •   c++
  •  | 
  •  
  •  | 
  •   ( words)

Question

I have two of these:

bool isPointOnShape(int a, int b)
{

}

bool isPointInShape(int a, int b)
{

}

Say I have a square, first point (bottom left corner) is x,y (0,0) second point (top left) is (0,2), third is (2,2) and fourth is (0,2).

The Points on shape would be (0,1) (1,2) (2,1) (1,0) and Points in shape is (1,1)

How do I find out the points on shape / in shape and return a true value so that I can store it somewhere?

Solution

I'll offer a general solution for any shape that can be divided in straight segments.

So, as you may have guessed, I'll start by consider your "shape" as a list of segments that completes a loop. Or simply put a circular list of points that represents a loop, for example, your square would be this list of points:

0, 0
0, 2
2, 2
2, 0

Note that we consider that there are segments from each point to the next and that the final point connects to the first. Also, we require that no consecutive points are equal, nor the first and last. If there are any, those must be removed before proceeding.


Now, for each segment we can determinate the bounding box. For example given this segment:

a = (0, 2)
b = (2, 2)

Then the range of values in x is [0, 2] and in y is [2, 2] and that is your bounding box for that segment.

The next thing you need is the director vector of the line of the segment. To get that, first calculate the length of the segment:

length = sqrt((a.x - b.x)*(a.x - b.x) + (a.y - b.y)*(a.y - b.y))

And then:

director.x = (a.x - b.x)/length
director.y = (a.y - b.y)/length

Note 1: when then length is 0, then you have an invalid segment. That's why we don't want repeated points.

Note 2: Using the director vector instead of using the equation of the line will make things easier.


Now, given a point p, you can determinate if that point is in a segment (if it is one of the points in the list). For the rest of cases we start by looking if it is inside of the axis aligned bounding box. This is done simply by checking the range:

if
(
    (p.x >= box.left && p.x <= box.right) &&
    (p.y >= box.top && p.y <= box.bottom) // with origin at the top-left corner
)
{
     //It is inside of the bounding box
}

And if it is, then we calculate the distance from the point to the line, if it is 0 then it is on the line. Now, because of floating point arithmetics, you could test if the distance is less or equal to epsilon, where epsilon is a very small number.

We use this formula:

distance vector = (a - p) - ((a - p) · director) * director
distance = the norm of distance vector

Where "·" denotes a dot product and "*" denotes an scalar product.

All that rests is to iterate over the segments, for each one calculate the distance and if for anyone the distance is less than epsilon then the point is "on the shape".


Ok, but what about "in the shape"?

Well, with a little help of a trick from topology we can determinate if a point is inside or not. This is the same algorithm Windows would use to fill a polygon or polyline (such as deciding what is inside a selected area with free hand in Microsoft Paint).

It goes like this:

Count the number of segments you have to cross to get to the point from outside. If the number is pair, then it is outside, if it is odd then inside.

You can choose from what direction to reach the point. I choose left.

Once more, you are going to iterate over the segments. For each one we need to decide if it is at the vertical range. For that use the bounding box:

if ((p.y >= box.top && p.y <= box.bottom))
{
     //In the vertical range
}

Now, determinate if the segment is at left, or right:

if (p.x < box.left)
{
     //The segment is at the left
}
else if (p.x > box.right)
{
     //The segment is at the right
}
else
{
     //The segment is close, need further calculation
}

In the case that the segment is close we need to calculate the vector distance to that segment and check it's direction.

The vector distance? Well, we already have it, we are taking its norm to determinate the distance. Now, instead of taking the norm, verify the sign of the x coordinate. If it is less than 0, it is right, if it is more than 0 then it is left. If it is 0... it means that the segment is horizontal (because the distance vector is always perpendicular to the segment), you can skip that segment*.

*: In fact, if the segment is horizontal and it is in vertical range, it means that it is at the segment. Are segments "in shape" or not?

Now, you need to count the number of segments at the left, and if it odd, the point is inside the shape. It is out otherwise. This can also be done with the segments that are up, or right, or below. I just picked left.


For large shapes where iterating over all the segments is expensive, you can store the segments in some space partitioning data structure. That is beyond the scope of this post.

OTHER TIPS

If I suppose you have a Rectangle class and that this class has members bottomLeft and topRight, you can write something like this:

bool Rectangle::isPointOnShape(int x, int y) {
    if (x == bottomLeft.x || x == topRight.x)
        if (y > bottomLeft.y && y < topRight.y)
            return true;

    if (y == bottomLeft.y || y == topRight.y)
        if (x > bottomLeft.x && x < topRight.x)
            return true;
}

bool Rectangle::isPointInShape(int x, int y) {
    bool inX = false;
    bool inY = false;
    if (x > bottomLeft.x && x < topRight.x)
        inX = true;

    if (y > bottomLeft.y && y < topRight.y)
        inY = true;

    return (inX && inY);
}

If your shape is not a rectangle, this functions will be different of course.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow