32

How would I write this function? Any examples appreciated

function isPointBetweenPoints(currPoint, point1, point2):Boolean {

    var currX = currPoint.x;
    var currY = currPoint.y;

    var p1X = point1.x;
    var p1y = point1.y;

    var p2X = point2.x;
    var p2y = point2.y;

    //here I'm stuck
}
andand
  • 17,134
  • 11
  • 53
  • 79
nuway
  • 2,324
  • 4
  • 27
  • 48
  • 2
    There are some good answers below, but I thought I'd point out that you should watch out for floating point precision issues. Whichever method you use, you'll probably have to allow a small amount of error when, for example, testing if two different slopes are the same. – Adrian McCarthy Aug 10 '12 at 20:45
  • @Adrian McCarthy: That's the major problem with slope-based methods. Slope changes non-uniformly with angle: the closer the line is to vertical, the faster the slope grows (not even mentioning the special case with vertical and almost vertical line). There's simply no good slope-based strategy. I'd avoid slope-based methods at all costs. – AnT stands with Russia Aug 11 '12 at 05:27
  • See also [python - How can you determine a point is between two other points on a line segment? - Stack Overflow](https://stackoverflow.com/questions/328107/how-can-you-determine-a-point-is-between-two-other-points-on-a-line-segment) – user202729 Jun 19 '22 at 11:16

8 Answers8

68

Assuming that point1 and point2 are different, first you check whether the point lies on the line. For that you simply need a "cross-product" of vectors point1 -> currPoint and point1 -> point2.

dxc = currPoint.x - point1.x;
dyc = currPoint.y - point1.y;

dxl = point2.x - point1.x;
dyl = point2.y - point1.y;

cross = dxc * dyl - dyc * dxl;

Your point lies on the line if and only if cross is equal to zero.

if (cross != 0)
  return false;

Now, as you know that the point does lie on the line, it is time to check whether it lies between the original points. This can be easily done by comparing the x coordinates, if the line is "more horizontal than vertical", or y coordinates otherwise

if (abs(dxl) >= abs(dyl))
  return dxl > 0 ? 
    point1.x <= currPoint.x && currPoint.x <= point2.x :
    point2.x <= currPoint.x && currPoint.x <= point1.x;
else
  return dyl > 0 ? 
    point1.y <= currPoint.y && currPoint.y <= point2.y :
    point2.y <= currPoint.y && currPoint.y <= point1.y;

Note that the above algorithm if entirely integral if the input data is integral, i.e. it requires no floating-point calculations for integer input. Beware of potential overflow when calculating cross though.

P.S. This algorithm is absolutely precise, meaning that it will reject points that lie very close to the line but not precisely on the line. Sometimes this is not what's needed. But that's a different story.

AnT stands with Russia
  • 312,472
  • 42
  • 525
  • 765
  • 7
    You can decrease algorithm precision by implementing threshold in validation of cross product So if cross product is almost zero, then the point is almost on the line `threshold = 0.1; if (abs(cross) > threshold) return false;`. – Matej Apr 01 '15 at 08:56
  • Could we simplify this? Since we know it's ON the line, why do we care if it's more horizontal than vertical? There can be only one y value for any given x on the line, so if `currPoint.x` is between `point1.x` and `point2.x`, how could it be anywhere other than on the line? – mkirk Jun 04 '16 at 02:00
  • 3
    @mkirk: "There can be only one y value for any given x on the line" - not true for a vertical line. If the segment is strictly vertical then range check for `x` produces no meaningful answer. Yes, one can always check `x` range, except that for a strictly vertical segment one has to check `y` range instead. My approach with "more horizontal"/"more vertical" is just a balanced generalization of that. – AnT stands with Russia Jun 04 '16 at 02:01
  • Thanks for the clarification @AnT, you're right! `if dxl != 0` might be more to the point, and slightly faster. – mkirk Jun 04 '16 at 02:29
  • Keep in mind the algorithm will work as if the line is infinite. If you need to verify if the point is between the two points as a segment, you need to use another algorithm. – Romel Pérez Mar 05 '18 at 23:51
  • 2
    @Romel Pérez: Not sure what you mean. The above answer clearly shows how to check if the point lies between the two original points. – AnT stands with Russia Mar 05 '18 at 23:54
  • @AnT: Thanks for the precise answer. Worked well for me :-) – Fred Dec 08 '19 at 15:52
  • Another way to check if the point is between original points is: ```sqrDistance(currentPoint, point1) <= sqrDistance(point1, point2) && sqrDistance(currentPoint, point2) <= sqrDistance(point1, point2)```Using squared distance to avoid floating point comparison – Marko Jan 14 '21 at 13:37
  • Should note you should not do `cross != 0` for floating point, you should do `abs(cross) < epsilon` – WDUK Jun 18 '22 at 17:37
37
   Distance(point1, currPoint)
 + Distance(currPoint, point2)
== Distance(point1, point2)

But be careful if you have floating point values, things are different for them...

When concerned about the computational cost of computing "the square roots", don't:
Just compare "the squares".

greybeard
  • 2,249
  • 8
  • 30
  • 66
maxim1000
  • 6,297
  • 1
  • 23
  • 19
  • 8
    Very nice, with floating point values (like in JavaScript) you can do something like `return distanceAC + distanceBC - distanceAB < THRESHOLD;` – mikhail Jan 14 '14 at 21:42
  • 1
    This method is more stable than AnT's answer above with the cross value. – CodeBrew Jan 28 '17 at 16:38
  • 1
    But it needs much more computing power (3 times square root). – Chrisstar Nov 08 '17 at 20:07
  • @Chrisstar, if square root is a bottle neck, the equation can be rewritten to avoid it - squaring both parts alone leaves only one square root, with more complication we can get rid of it too. – maxim1000 Nov 11 '17 at 08:37
  • for improving the speed compare the squareDistances : abs(a * a + b * b - c * c) < EPSILON : where EPSILON = appropriate small value – Deian Oct 29 '21 at 13:54
  • `abs(a * a + b * b - c * c) < EPSILON` doesn't work for me. @Deian – WDUK Apr 08 '22 at 09:43
  • @WDUK you need to elaborate a bit more than that :) – Deian Apr 08 '22 at 16:56
  • a * a + b * b = c * c . Sounds like pythagorus theorem, that satisfy right angled triangle. Is that an edge case? – Anshu Saurabh Nov 01 '22 at 15:10
5

This is independent of Javascript. Try the following algorithm, with points p1=point1 and p2=point2, and your third point being p3=currPoint:

v1 = p2 - p1
v2 = p3 - p1
v3 = p3 - p2
if (dot(v2,v1)>0 and dot(v3,v1)<0) return between
else return not between

If you want to be sure it's on the line segment between p1 and p2 as well:

v1 = normalize(p2 - p1)
v2 = normalize(p3 - p1)
v3 = p3 - p2
if (fabs(dot(v2,v1)-1.0)<EPS and dot(v3,v1)<0) return between
else return not between
geometrian
  • 14,775
  • 10
  • 56
  • 132
  • can you please tell me what exactly "normalize" means? If I'm not mistaken, "dot" is a [dot product](https://en.wikipedia.org/wiki/Dot_product), "fabs" is an [Absolute value of a number](https://en.wikipedia.org/wiki/Absolute_value). – ZoomAll Feb 13 '23 at 13:19
  • 1
    @ZoomAll It means to normalize the vector—i.e. to rescale it to length `1`. You can normalize a vector by dividing by its length. Something like: `function normalize(vec) { const len=length(vec); return Vec3( vec.x/len, vec.y/len, vec.z/len ); }`, where `length(...)` gets the Euclidean length of the vector. – geometrian Feb 13 '23 at 18:16
4

You want to check whether the slope from point1 to currPoint is the same as the slope from currPoint to point2, so:

m1 = (currY - p1Y) / (currX - p1X);
m2 = (p2Y - currY) / (p2X - currX);

You also want to check whether currPoint is inside the box created by the other two, so:

return (m1 == m2) && (p1Y <= currY && currY <= p2Y) && (p1X <= currX && currX <= p2X);

Edit: This is not a very good method; look at maxim1000's solution for a much more correct way.

Community
  • 1
  • 1
Christian Mann
  • 8,030
  • 5
  • 41
  • 51
  • Yeah, I just realized my mistake. I think I can add another constraint in to fix that. – Christian Mann Aug 10 '12 at 19:27
  • But the input data does not state that `p1X <= p2X` and `p1Y <= p2Y` (moreover, it simply cannot be true in all cases). Yet, your final check will only work correctly if those conditions are met. On top of that, your algorithm relies on direct precise comparison of floating-point values `m1` and `m2`. Needless to say, this simply will not work due to imprecise nature of floating-point calculations. – AnT stands with Russia Aug 10 '12 at 22:37
  • ... not even mentioning that the slope-based method can't handle vertical lines. What about division by zero? – AnT stands with Russia Aug 11 '12 at 05:28
  • @AnT since the required condition is ```m1 == m2``` you you can reformulate it as ```(currY - p1Y) * (p2X - currX) == (currX - p1X) * (p2Y - currY)``` . By doing this you are avoiding floating points comparison and division by zero, so slope-based method is perfectly valid. The second condition that should be satisfied is that ```sqrDistance(p1, curr) <= sqrDistance(p1,p2) && sqrDistance(p2, curr) <= sqrDistance(p1,p2)```. This way you ensure that currPoint is between p1 and p2. Using sqrDistance (squared distance) in order to avoid floating points – Marko Jan 14 '21 at 13:39
4

I'll use Triangle approach: Triangle approach

First, I'll check the Area, if the Area is close to 0, then the Point lies on the Line.

But think about the case where the length of AC is so great, then the Area increases far from 0, but visually, we still see that B is on AC: that when we need to check the height of the triangle.

To do this, we need to remember the formula we learn from first grade: Area = Base * Height / 2

Here is the code:

    bool Is3PointOn1Line(IList<Vector2> arrVert, int idx1, int idx2, int idx3)
    {
        //check if the area of the ABC triangle is 0:
        float fArea = arrVert[idx1].x * (arrVert[idx2].y - arrVert[idx3].y) +
            arrVert[idx2].x * (arrVert[idx3].y - arrVert[idx1].y) +
            arrVert[idx3].x * (arrVert[idx1].y - arrVert[idx2].y);
        fArea = Mathf.Abs(fArea);
        if (fArea < SS.EPSILON)
        {
            //Area is zero then it's the line
            return true;
        }
        else
        {
            //Check the height, in case the triangle has long base
            float fBase = Vector2.Distance(arrVert[idx1], arrVert[idx3]);
            float height = 2.0f * fArea / fBase;
            return height < SS.EPSILON;
        }
    }

Usage:

Vector2[] arrVert = new Vector2[3];

arrVert[0] = //...
arrVert[1] = //...
arrVert[2] = //...

if(Is3PointOn1Line(arrVert, 0, 1, 2))
{
    //Ta-da, they're on same line
}

PS: SS.EPSILON = 0.01f and I use some function of Unity (for ex: Vector2.Distance), but you got the idea.

123iamking
  • 2,387
  • 4
  • 36
  • 56
  • Very interesting! But I'm wondering to know what happens if we try to check a point that is on the C right side. Is possible we get 0 forthis point as well? – Lucas Sousa Mar 10 '20 at 12:54
  • @LucasSousa : Do you mean to ask "If B is on the right of C instead of in middle of A & C, then Is the function still correct?"? If so, the function's still correct, it doesn't matter if B is in between A & C or not. – 123iamking Mar 12 '20 at 04:02
3

Ready for what seems to be infinitely simpler than some of these other solutions?

You pass it three points (three objects with an x and y property). Points 1 and 2 define your line, and point 3 is the point you are testing.

function pointOnLine(pt1, pt2, pt3) {
    const dx = (pt3.x - pt1.x) / (pt2.x - pt1.x);
    const dy = (pt3.y - pt1.y) / (pt2.y - pt1.y);
    const onLine = dx === dy

    // Check on or within x and y bounds
    const betweenX = 0 <= dx && dx <= 1;
    const betweenY = 0 <= dy && dy <= 1;

    return onLine && betweenX && betweenY;
}

console.log('pointOnLine({x: 0, y: 0}, {x: 1, y: 1}, {x: 2, y: 2})');
console.log(pointOnLine({ x: 0, y: 0 }, { x: 1, y: 1 }, { x: 2, y: 2 }));

console.log('pointOnLine({x: 0, y: 0}, {x: 1, y: 1}, {x: 0.5, y: 0.5})');
console.log(pointOnLine({ x: 0, y: 0 }, { x: 1, y: 1 }, { x: 0.5, y: 0.5 }));

Edit: Simplified further according to RBarryYoung's observation.

Steve
  • 4,372
  • 26
  • 37
  • 1
    Do you really need the between variables? Isn't it sufficient to check that `dx` and `dy` are between 0.0 and 1.0? and since `dx === dy` you only need to check one of them? – RBarryYoung Jun 24 '21 at 13:51
  • Nice @RBarryYoung, simpler again! I've updated the answer. I've kept the variables for readability and easier interpretation. – Steve Jun 25 '21 at 00:42
  • I expect this to fail for horizontal and vertical lines as defined by p1 and p2. – greybeard Sep 11 '21 at 05:25
  • This throws division by zero for vertical and horizontal lines – Yoctometric Apr 17 '22 at 19:56
2

This approach is similar to Steve's approach, just shorter and improved to use as little memory and process power as possible. But first the mathematical idea:

Let a, b be the ends of the line, ab the difference between them and p the point to check. Then p is exactly then on the line, if

a + i * ab = p

with i being a number in the interval [0;1] representing the index on the line. We can write that as two separate equations (for 2D):

a.x + i * ab.x = p.x
a.y + i * ab.y = p.y

i = (p.x - a.x) / ab.x
i = (p.y - a.y) / ab.y

Which gives us to requirements for p to be on the line from a to b:

(p.x - a.x) / ab.x = (p.y - a.y) / ab.y
and
0 ≤ i ≤ 1

In code:

function onLine(a, b, p) {
    var i1 = (p.x - a.x) / (b.x - a.x), i2 = (p.y - a.y) / (b.y - a.y);
    return i1 == i2 && i1 <= 0 && i1 >= 1;
}

Technically you could even inline i2 but that makes it even harder to read.

greybeard
  • 2,249
  • 8
  • 30
  • 66
RcCookie
  • 59
  • 1
  • 10
0
  private static boolean pointInLine(int2 lineBegin, int2 lineEnd, int2 point)
  { boolean result = false;
    
    int2 b = int2(min(lineBegin.x, lineEnd.x), min(lineBegin.y, lineEnd.y));
    int2 e = int2(max(lineBegin.x, lineEnd.x), max(lineBegin.y, lineEnd.y));
    if (point.x >= b.x && point.x <= e.x &&
        point.y >= b.y && point.y <= e.y)
    { int2 normal = lineEnd.sub(lineBegin).perp();
      int2 d0     = lineBegin.sub(point);
      if (d0.dot(normal) == 0x0)
      { result = true;
      }
    }
    return result;
  }

This works for any slope, even if lineBegin == lineEnd == point.

  • First, create two points b and e, which ensures b <= e. (This is two support lines that have a negative slope) Check if point lands on the box (inclusive) created by those two points.
  • Then, get the normal of the line, which is a vector perpendicular to it. You can do this by negating the x and transposing, x,y --> y, -x.
  • Then you can simply create a vector that points to the line from the point or to the point from the line, it doesn't matter and doesn't have to be from the center of the line. Once you do that check if the vector is perpendicular to the normal by getting the dot product of the normal and the point vector.

It also makes it a bit easier on you if you've got some sort of math lib or at least a struct with x,y components. But you can do this with scalar components aswell. But getting the dot product and the normal are very simple calculations.

Here's the dot product: .dot(int2 v) = (x * v.x + y * v.y)

Here's the perpendicular: .perp() = new int2(y, -x)

.sub() .add() Do what you'd expect and params are in the same order.

MicroRJ
  • 175
  • 11