Как определить точку между двумя другими точками на отрезке линии?

96

Предположим, у вас есть двухмерная плоскость с 2 точками (называемыми a и b), представленная целым числом x и целым числом y для каждой точки.

Как вы можете определить, находится ли другая точка c на отрезке, определяемом элементами a и b?

Я чаще всего использую python, но мне бы пригодились примеры на любом языке.

Пол Д. Иден
источник
4
Я вижу, что в этих ответах происходит ОЧЕНЬ МНОГО длинных = sqrt (x); они могут работать, но они не быстрые. Рассмотрите возможность использования квадрата длины; если вы просто сравниваете значения квадратов длины друг с другом, нет потери точности и вы сохраняете медленные вызовы sqrt ().
ojrac
1
Представлена ​​ли точка c двумя целыми числами? Если да, то хотите ли вы знать, находится ли c точно вдоль реальной прямой линии между a и b или лежит на растровой аппроксимации прямой линии между a и b? Это важное уточнение.
RobS 02
Аналогичный вопрос был задан здесь: stackoverflow.com/q/31346862/1914034 с решением, когда требуется буферное расстояние от линии
ниже радара
1
Предупреждение для будущих читателей: изрядное количество ответов неверны или неполны. Несколько крайних случаев, которые часто не работают, - это горизонтальные и вертикальные линии.
Stefnotch

Ответы:

129

Убедитесь, что перекрестное произведение (ba) и (ca) равно 0, как говорит Дариус Бэкон, говорит вам, выровнены ли точки a, b и c.

Но, поскольку вы хотите знать, находится ли c между a и b, вы также должны проверить, что скалярное произведение (ba) и (ca) положительно и меньше квадрата расстояния между a и b.

В неоптимизированном псевдокоде:

def isBetween(a, b, c):
    crossproduct = (c.y - a.y) * (b.x - a.x) - (c.x - a.x) * (b.y - a.y)

    # compare versus epsilon for floating point values, or != 0 if using integers
    if abs(crossproduct) > epsilon:
        return False

    dotproduct = (c.x - a.x) * (b.x - a.x) + (c.y - a.y)*(b.y - a.y)
    if dotproduct < 0:
        return False

    squaredlengthba = (b.x - a.x)*(b.x - a.x) + (b.y - a.y)*(b.y - a.y)
    if dotproduct > squaredlengthba:
        return False

    return True
Сирил Ка
источник
5
-epsilon < crossproduct < epsilon and min(a.x, b.x) <= c.x <= max(a.x, b.x) and min(a.y, b.y) <= c.y <= max(a.y, b.y)достаточно, не так ли?
jfs
9
Абсолютное значение перекрестного произведения в два раза больше площади треугольника, образованного тремя точками (со знаком, указывающим сторону третьей точки), поэтому IMHO вы должны использовать эпсилон, который пропорционален расстоянию между двумя конечными точками.
Барт
2
Вы можете сказать нам, почему это не работает с целыми числами? Я не вижу проблемы при условии, что проверка epsilon заменена на "! = 0".
Cyrille Ka
2
Да, лишние скобки - это просто опечатка. Прошло 4 года, прежде чем кто-то что-то сказал. :)
Cyrille Ka
4
Вы должны переименовать a, b, c, чтобы было понятнее, какие точки являются конечными точками сегмента, а какие - точкой запроса.
Craig Gidney
50

Вот как бы я это сделал:

def distance(a,b):
    return sqrt((a.x - b.x)**2 + (a.y - b.y)**2)

def is_between(a,c,b):
    return distance(a,c) + distance(c,b) == distance(a,b)
Жюль
источник
7
Единственная проблема с этим - числовая стабильность - учет разностей чисел и т. Д. Может привести к потере точности.
Джонатан Леффлер
29
-epsilon < (distance(a, c) + distance(c, b) - distance(a, b)) < epsilon
jfs
1
@jfs, что вы имеете в виду? Для чего нужен чек с эпсилоном?
Neon Warge
3
@NeonWarge: sqrt () возвращает число с плавающей запятой. ==в большинстве случаев не подходит для поплавков . math.isclose()можно было бы использовать вместо этого. В math.isclose()2008 году не было, поэтому я указал явное неравенство с epsilon( abs_tolв math.isclose()разговоре).
jfs
Это абсолютно правильно, однако этот метод не очень хорош даже с math.isclose (). Когда координаты являются целыми числами, вы хотели бы провести точный тест. Другой мой ответ дает формулу для этого: stackoverflow.com/a/29301940/40078
Жюль
36

Проверьте, равно ли перекрестное произведение b-aи : это означает, что все точки коллинеарны. Если да, проверьте, находятся ли координаты между точками «s» и «s». Используйте координаты x или y, если и являются отдельными на этой оси (или они одинаковы для обеих).c-a0cabab

def is_on(a, b, c):
    "Return true iff point c intersects the line segment from a to b."
    # (or the degenerate case that all 3 points are coincident)
    return (collinear(a, b, c)
            and (within(a.x, c.x, b.x) if a.x != b.x else 
                 within(a.y, c.y, b.y)))

def collinear(a, b, c):
    "Return true iff a, b, and c all lie on the same line."
    return (b.x - a.x) * (c.y - a.y) == (c.x - a.x) * (b.y - a.y)

def within(p, q, r):
    "Return true iff q is between p and r (inclusive)."
    return p <= q <= r or r <= q <= p

Раньше этот ответ состоял из трех обновлений. Полезная информация от них: глава Брайана Хейса в « Beautiful Code» описывает пространство дизайна для функции проверки коллинеарности - полезный фон. Ответ Винсента помог улучшить это. И именно Хейс предложил проверить только одну из координат x или y; изначально код имел andместо вместо if a.x != b.x else.

Дариус Бэкон
источник
Поскольку проверка диапазона выполняется быстрее, было бы лучше сначала проверить диапазон, а затем проверить коллинеарность в ограничивающей рамке.
Grant M
1
Функция is_on (a, b, c) неверна для случая, когда a == b! = C. В таком случае он вернет true, даже если c не пересекает отрезок прямой от a до b.
Микко Вирккиля
@SuperFlux, я просто попробовал запустить это, и получил False.
Дариус Бэкон
2
Я думаю, что этот ответ явно превосходит текущий принятый ответ.
Рик поддерживает Монику
1
collinear (a, b, c) проверяет числа с плавающей запятой на равенство. Разве не следует использовать эпсилон / допуск?
jwezorek
7

Вот еще один подход:

  • Предположим, что две точки - это A (x1, y1) и B (x2, y2)
  • Уравнение прямой, проходящей через эти точки: (x-x1) / (y-y1) = (x2-x1) / (y2-y1) .. (просто приравнивая наклоны)

Точка C (x3, y3) будет находиться между A и B, если:

  • x3, y3 удовлетворяет приведенному выше уравнению.
  • x3 лежит между x1 и x2, а y3 лежит между y1 и y2 (тривиальная проверка)
Шридхар Айер
источник
При этом не учитываются ошибки округления (неточность координат).
Барт
Я думаю, это правильная идея, но в ней мало деталей (как мы можем проверить это уравнение на практике?) И немного ошибочно: последний y3 должен быть y2.
Дариус Бэкон
@Darius: исправил эту опечатку
Harley Holcombe,
7

Длина сегмента не важна, поэтому использование квадратного корня не требуется, и его следует избегать, поскольку мы можем потерять некоторую точность.

class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y

class Segment:
    def __init__(self, a, b):
        self.a = a
        self.b = b

    def is_between(self, c):
        # Check if slope of a to c is the same as a to b ;
        # that is, when moving from a.x to c.x, c.y must be proportionally
        # increased than it takes to get from a.x to b.x .

        # Then, c.x must be between a.x and b.x, and c.y must be between a.y and b.y.
        # => c is after a and before b, or the opposite
        # that is, the absolute value of cmp(a, b) + cmp(b, c) is either 0 ( 1 + -1 )
        #    or 1 ( c == a or c == b)

        a, b = self.a, self.b             

        return ((b.x - a.x) * (c.y - a.y) == (c.x - a.x) * (b.y - a.y) and 
                abs(cmp(a.x, c.x) + cmp(b.x, c.x)) <= 1 and
                abs(cmp(a.y, c.y) + cmp(b.y, c.y)) <= 1)

Случайный пример использования:

a = Point(0,0)
b = Point(50,100)
c = Point(25,50)
d = Point(0,8)

print Segment(a,b).is_between(c)
print Segment(a,b).is_between(d)
винсент
источник
1
Если cx или cy имеют значение float, то первый ==in is_between()может не сработать (кстати, это замаскированный перекрестный продукт).
jfs
добавить в is_between():a, b = self.a, self.b
jfs
Похоже, это вернет истину, если все три точки одинаковы (что нормально, имхо), но ложь, если ровно две из точек одинаковы - довольно непоследовательный способ определения промежуточности. Я разместил альтернативу в своем ответе.
Дариус Бэкон
исправил это с помощью другого трюка cmp, но этот код начинает пахнуть ;-)
Винсент
5

Вот другой способ сделать это с кодом на C ++. Учитывая две точки, l1 и l2, легко выразить отрезок прямой между ними как

l1 + A(l2 - l1)

где 0 <= A <= 1. Это известно как векторное представление линии, если вас интересует не только его использование для этой задачи. Мы можем разделить его на компоненты x и y, получив:

x = l1.x + A(l2.x - l1.x)
y = l1.y + A(l2.y - l1.y)

Возьмите точку (x, y) и подставьте ее компоненты x и y в эти два выражения, чтобы найти A. Точка находится на линии, если решения для A в обоих выражениях равны и 0 <= A <= 1. Поскольку решение для A требует деления, есть особые случаи, когда требуется обработка, чтобы остановить деление на ноль, когда сегмент линии является горизонтальным или вертикальным. Окончательное решение выглядит следующим образом:

// Vec2 is a simple x/y struct - it could very well be named Point for this use

bool isBetween(double a, double b, double c) {
    // return if c is between a and b
    double larger = (a >= b) ? a : b;
    double smaller = (a != larger) ? a : b;

    return c <= larger && c >= smaller;
}

bool pointOnLine(Vec2<double> p, Vec2<double> l1, Vec2<double> l2) {
    if(l2.x - l1.x == 0) return isBetween(l1.y, l2.y, p.y); // vertical line
    if(l2.y - l1.y == 0) return isBetween(l1.x, l2.x, p.x); // horizontal line

    double Ax = (p.x - l1.x) / (l2.x - l1.x);
    double Ay = (p.y - l1.y) / (l2.y - l1.y);

    // We want Ax == Ay, so check if the difference is very small (floating
    // point comparison is fun!)

    return fabs(Ax - Ay) < 0.000001 && Ax >= 0.0 && Ax <= 1.0;
}
Мэтью Генри
источник
4

Используя более геометрический подход, рассчитайте следующие расстояния:

ab = sqrt((a.x-b.x)**2 + (a.y-b.y)**2)
ac = sqrt((a.x-c.x)**2 + (a.y-c.y)**2)
bc = sqrt((b.x-c.x)**2 + (b.y-c.y)**2)

и проверьте, равно ли ac + bc ab :

is_on_segment = abs(ac + bc - ab) < EPSILON

Это потому, что есть три возможности:

  • Три точки образуют треугольник => ac + bc> ab
  • Они коллинеарны, а c находится за пределами отрезка ab => ac + bc> ab
  • Они коллинеарны и c находится внутри отрезка ab => ac + bc = ab
эфотинис
источник
Как упоминает Джонатан Леффлер в другом комментарии, у этого есть числовые проблемы, которых избегают другие подходы, такие как перекрестное произведение. Глава, на которую я ссылаюсь в своем ответе, объясняет.
Дариус Бэкон
3

Хорошо, много упоминаний линейной алгебры (перекрестное произведение векторов), и это работает в реальном (т.е. непрерывном или с плавающей запятой) пространстве, но в вопросе конкретно говорилось, что две точки были выражены как целые числа, и, таким образом, перекрестное произведение не является правильным решение, хотя оно может дать приблизительное решение.

Правильное решение - использовать алгоритм линии Брезенхема между двумя точками и посмотреть, является ли третья точка одной из точек на линии. Если точки находятся на достаточно большом расстоянии, что вычисление алгоритма неэффективно (а для этого он должен быть действительно большим), я уверен, что вы могли бы покопаться и найти оптимизацию.

Cletus
источник
Он решает, как провести линию через двумерное целочисленное пространство между двумя произвольными точками, и это математически правильно. Если третья точка является одной из точек на этой линии, то она по определению находится между этими двумя точками.
Cletus 01
1
Нет, алгоритм линии Брезенхема решает, как создать приближение отрезка линии в двумерном целочисленном пространстве. Из сообщения оригинального плаката я не вижу, что это был вопрос о растеризации.
Cyrille Ka
«Допустим, у вас есть двухмерная плоскость с 2 точками (называемыми a и b), представленная x INTEGER и ay INTEGER для каждой точки». (курсив добавлен мной).
cletus
1
Я думаю, что алгоритм линий Брезенхема дает целые точки шкафа для линии, которые затем можно использовать для рисования линии. Они могут не быть на связи. Например, если для значений от (0,0) до (11,13) алгоритм выдаст количество пикселей для рисования, но нет целых точек, кроме конечных точек, потому что 11 и 13 взаимно просты.
Grant M
Как может решение, правильное для реального пространства (ℝ × ℝ), не быть правильным для целочисленного пространства (ℕ × ℕ), как ℕ∈ℝ. Или вы имеете в виду: «не оптимально для…» вместо «неправильно»?
Идеограмма
2

Скалярное произведение между (ca) и (ba) должно быть равно произведению их длин (это означает, что векторы (ca) и (ba) выровнены и имеют одинаковое направление). Более того, длина (ca) должна быть меньше или равна длине (ba). Псевдокод:

# epsilon = small constant

def isBetween(a, b, c):
    lengthca2  = (c.x - a.x)*(c.x - a.x) + (c.y - a.y)*(c.y - a.y)
    lengthba2  = (b.x - a.x)*(b.x - a.x) + (b.y - a.y)*(b.y - a.y)
    if lengthca2 > lengthba2: return False
    dotproduct = (c.x - a.x)*(b.x - a.x) + (c.y - a.y)*(b.y - a.y)
    if dotproduct < 0.0: return False
    if abs(dotproduct*dotproduct - lengthca2*lengthba2) > epsilon: return False 
    return True
Федерико А. Рампони
источник
Разве последнее условие не должно быть больше похоже на: ABS (product - lengthca * lengthba) <epsilon?
Джонатан Леффлер
Разве вам не следует вместо этого сравнивать квадрат длины? Следует избегать квадратных корней. Также, если это неизбежно из-за переполнения, вы можете использовать math.hypot вместо math.sqrt (с соответствующим изменением аргументов).
Дариус Бэкон
Меня тоже интересует этот эпсилон. Вы можете это объяснить? Конечно, если мы должны иметь дело с числами с плавающей запятой, мы должны быть осторожны с сравнениями, но мне не ясно, почему эпсилон делает это конкретное сравнение более точным.
Дариус Бэкон
Я согласен. На этот вопрос есть несколько хороших ответов, и этот хороший. Но этот код нужно изменить, чтобы не использовать sqrt, и последнее сравнение исправлено.
Cyrille Ka
@Jonathan: действительно, код более знакомый и элегантный, используя abs. Спасибо.
Федерико А. Рампони
2

Мне это нужно для javascript для использования в холсте html5 для определения, находится ли курсор пользователя над определенной строкой или рядом с ней. Поэтому я преобразовал ответ Дариуса Бэкона в coffeescript:

is_on = (a,b,c) ->
    # "Return true if point c intersects the line segment from a to b."
    # (or the degenerate case that all 3 points are coincident)
    return (collinear(a,b,c) and withincheck(a,b,c))

withincheck = (a,b,c) ->
    if a[0] != b[0]
        within(a[0],c[0],b[0]) 
    else 
        within(a[1],c[1],b[1])

collinear = (a,b,c) ->
    # "Return true if a, b, and c all lie on the same line."
    ((b[0]-a[0])*(c[1]-a[1]) < (c[0]-a[0])*(b[1]-a[1]) + 1000) and ((b[0]-a[0])*(c[1]-a[1]) > (c[0]-a[0])*(b[1]-a[1]) - 1000)

within = (p,q,r) ->
    # "Return true if q is between p and r (inclusive)."
    p <= q <= r or r <= q <= p
bfcoder
источник
2

Вы можете использовать клин и точечное произведение:

def dot(v,w): return v.x*w.x + v.y*w.y
def wedge(v,w): return v.x*w.y - v.y*w.x

def is_between(a,b,c):
   v = a - b
   w = b - c
   return wedge(v,w) == 0 and dot(v,w) > 0
Жюль
источник
1

Вот как я делал это в школе. Я забыл, почему это плохая идея.

РЕДАКТИРОВАТЬ:

@Darius Bacon: цитирует книгу «Красивый код», в которой объясняется, почему приведенный ниже код не является хорошей идеей.

#!/usr/bin/env python
from __future__ import division

epsilon = 1e-6

class Point:
    def __init__(self, x, y):
        self.x, self.y = x, y

class LineSegment:
    """
    >>> ls = LineSegment(Point(0,0), Point(2,4))
    >>> Point(1, 2) in ls
    True
    >>> Point(.5, 1) in ls
    True
    >>> Point(.5, 1.1) in ls
    False
    >>> Point(-1, -2) in ls
    False
    >>> Point(.1, 0.20000001) in ls
    True
    >>> Point(.1, 0.2001) in ls
    False
    >>> ls = LineSegment(Point(1, 1), Point(3, 5))
    >>> Point(2, 3) in ls
    True
    >>> Point(1.5, 2) in ls
    True
    >>> Point(0, -1) in ls
    False
    >>> ls = LineSegment(Point(1, 2), Point(1, 10))
    >>> Point(1, 6) in ls
    True
    >>> Point(1, 1) in ls
    False
    >>> Point(2, 6) in ls 
    False
    >>> ls = LineSegment(Point(-1, 10), Point(5, 10))
    >>> Point(3, 10) in ls
    True
    >>> Point(6, 10) in ls
    False
    >>> Point(5, 10) in ls
    True
    >>> Point(3, 11) in ls
    False
    """
    def __init__(self, a, b):
        if a.x > b.x:
            a, b = b, a
        (self.x0, self.y0, self.x1, self.y1) = (a.x, a.y, b.x, b.y)
        self.slope = (self.y1 - self.y0) / (self.x1 - self.x0) if self.x1 != self.x0 else None

    def __contains__(self, c):
        return (self.x0 <= c.x <= self.x1 and
                min(self.y0, self.y1) <= c.y <= max(self.y0, self.y1) and
                (not self.slope or -epsilon < (c.y - self.y(c.x)) < epsilon))

    def y(self, x):        
        return self.slope * (x - self.x0) + self.y0

if __name__ == '__main__':
    import  doctest
    doctest.testmod()
jfs
источник
1

Любая точка на отрезке ( a , b ) (где a и b - векторы) может быть выражена как линейная комбинация двух векторов a и b :

Другими словами, если c лежит на отрезке ( a , b ):

c = ma + (1 - m)b, where 0 <= m <= 1

Решая относительно m , получаем:

m = (c.x - b.x)/(a.x - b.x) = (c.y - b.y)/(a.y - b.y)

Итак, наш тест выглядит (на Python):

def is_on(a, b, c):
    """Is c on the line segment ab?"""

    def _is_zero( val ):
        return -epsilon < val < epsilon

    x1 = a.x - b.x
    x2 = c.x - b.x
    y1 = a.y - b.y
    y2 = c.y - b.y

    if _is_zero(x1) and _is_zero(y1):
        # a and b are the same point:
        # so check that c is the same as a and b
        return _is_zero(x2) and _is_zero(y2)

    if _is_zero(x1):
        # a and b are on same vertical line
        m2 = y2 * 1.0 / y1
        return _is_zero(x2) and 0 <= m2 <= 1
    elif _is_zero(y1):
        # a and b are on same horizontal line
        m1 = x2 * 1.0 / x1
        return _is_zero(y2) and 0 <= m1 <= 1
    else:
        m1 = x2 * 1.0 / x1
        if m1 < 0 or m1 > 1:
            return False
        m2 = y2 * 1.0 / y1
        return _is_zero(m2 - m1)
Шанкстер
источник
1

c # Из http://www.faqs.org/faqs/graphics/algorithms-faq/ -> Тема 1.02: Как найти расстояние от точки до линии?

Boolean Contains(PointF from, PointF to, PointF pt, double epsilon)
        {

            double segmentLengthSqr = (to.X - from.X) * (to.X - from.X) + (to.Y - from.Y) * (to.Y - from.Y);
            double r = ((pt.X - from.X) * (to.X - from.X) + (pt.Y - from.Y) * (to.Y - from.Y)) / segmentLengthSqr;
            if(r<0 || r>1) return false;
            double sl = ((from.Y - pt.Y) * (to.X - from.X) - (from.X - pt.X) * (to.Y - from.Y)) / System.Math.Sqrt(segmentLengthSqr);
            return -epsilon <= sl && sl <= epsilon;
        }
Эдид
источник
Правильный способ избежать проблем с точностью в большинстве других подходов. Также значительно более эффективен, чем большинство других подходов.
Робин Дэвис
1

Вот код Java, который у меня сработал:

boolean liesOnSegment(Coordinate a, Coordinate b, Coordinate  c) {

    double dotProduct = (c.x - a.x) * (c.x - b.x) + (c.y - a.y) * (c.y - b.y);
    if (dotProduct < 0) return true;
    return false;
}
Голвиг
источник
1
dotProduct может сказать только о выравнивании. Ваш код неполный !!! С a (0,0), b (4,0), c (1,1) у вас есть dotproduct = (1-0) * (1-4) + (1-0) * (1-0) = - 3 + 1 = -3
user43968 02
0

как насчет того, чтобы просто убедиться, что угол наклона такой же, а точка находится между остальными?

заданные точки (x1, y1) и (x2, y2) (с x2> x1) и точка-кандидат (a, b)

если (b-y1) / (a-x1) = (y2-y2) / (x2-x1) и x1 <a <x2

Тогда (a, b) должен быть на линии между (x1, y1) и (x2, y2)

Чарльз Бретана
источник
Как насчет сумасшедших проблем с точностью с плавающей запятой, когда некоторые координаты близки или идентичны?
Робин Дэвис
Компьютеры плохо справляются с плавающей запятой. В компьютере нет такой вещи, как бесконечно плавно регулируемые значения. Поэтому, если вы используете плавающие точки, вы должны установить определение и использовать небольшое значение эпсилон в качестве детерминанта, и любые две точки ближе, чем это эпсилон, следует рассматривать как одну и ту же точку. Определите точку, которая находится на той же линии и на том же расстоянии от конечных точек. Если ваша кандидатная точка находится в пределах вашего эпсилона от этой вычисленной точки, назовите ее идентичной.
Чарльз Бретана
Я хотел сказать, что этот ответ непригоден для использования из-за проблем с точностью, когда вы фактически реализуете его в коде. Так что никто не должен его использовать. Прекрасный ответ на тест по математике. Но полный провал в компьютерном курсе. Я пришел сюда в поисках метода скалярного произведения (что верно); поэтому я подумал, что займу несколько минут, чтобы отметить многие ответы в этой цепочке, которые неверны, чтобы другие, знакомые с правильным решением, не испытали соблазна их использовать.
Робин Дэвис,
Вы правы в вопросах, возникающих из-за того, что компьютеры не могут представить все возможные действительные числа в строке. Вы ошибаетесь, что любое решение (включая метод скалярного произведения) может быть невосприимчивым к этим проблемам. Любое решение может пострадать от этих проблем. Если вы не сделаете некоторые допущения для приемлемого эпсилон, точка, которая находится точно на линии (но чьи координаты не представлены в двоичном представлении с плавающей запятой ieee), также не пройдет тест скалярного произведения, так как компьютер будет представлять координаты точки неточно. на некоторую сумму.
Чарльз Бретана
0

Ответ на C # с использованием класса Vector2D

public static bool IsOnSegment(this Segment2D @this, Point2D c, double tolerance)
{
     var distanceSquared = tolerance*tolerance;
     // Start of segment to test point vector
     var v = new Vector2D( @this.P0, c ).To3D();
     // Segment vector
     var s = new Vector2D( @this.P0, @this.P1 ).To3D();
     // Dot product of s
     var ss = s*s;
     // k is the scalar we multiply s by to get the projection of c onto s
     // where we assume s is an infinte line
     var k = v*s/ss;
     // Convert our tolerance to the units of the scalar quanity k
     var kd = tolerance / Math.Sqrt( ss );
     // Check that the projection is within the bounds
     if (k <= -kd || k >= (1+kd))
     {
        return false;
     }
     // Find the projection point
     var p = k*s;
     // Find the vector between test point and it's projection
     var vp = (v - p);
     // Check the distance is within tolerance.
     return vp * vp < distanceSquared;
}

Обратите внимание, что

s * s

- скалярное произведение вектора сегмента через перегрузку оператора в C #

Ключ состоит в том, чтобы воспользоваться преимуществом проекции точки на бесконечную линию и заметить, что скалярная величина проекции тривиально говорит нам, находится ли проекция на отрезке или нет. Мы можем настроить границы скалярной величины, чтобы использовать допуск нечеткости.

Если проекция находится в пределах границ, мы просто проверяем, находится ли расстояние от точки до проекции в пределах границ.

Преимущество метода перекрестного произведения в том, что допуск имеет значимое значение.

брэдгонсерфинг
источник
0

Вот мое решение с C # в Unity.

private bool _isPointOnLine( Vector2 ptLineStart, Vector2 ptLineEnd, Vector2 ptPoint )
{
    bool bRes = false;
    if((Mathf.Approximately(ptPoint.x, ptLineStart.x) || Mathf.Approximately(ptPoint.x, ptLineEnd.x)))
    {
        if(ptPoint.y > ptLineStart.y && ptPoint.y < ptLineEnd.y)
        {
            bRes = true;
        }
    }
    else if((Mathf.Approximately(ptPoint.y, ptLineStart.y) || Mathf.Approximately(ptPoint.y, ptLineEnd.y)))
    {
        if(ptPoint.x > ptLineStart.x && ptPoint.x < ptLineEnd.x)
        {
            bRes = true;
        }
    }
    return bRes;
}
калейдос
источник
Похоже, этот код будет работать только с вертикальными и горизонтальными отрезками линий. Что, если ptLineStart равен (0,0), ptLineEnd равен (2,2), а ptPoint равен (1, 1)?
vac
0

С # версия ответа Жюля:

public static double CalcDistanceBetween2Points(double x1, double y1, double x2, double y2)
{
    return Math.Sqrt(Math.Pow (x1-x2, 2) + Math.Pow (y1-y2, 2));
}

public static bool PointLinesOnLine (double x, double y, double x1, double y1, double x2, double y2, double allowedDistanceDifference)
{
    double dist1 = CalcDistanceBetween2Points(x, y, x1, y1);
    double dist2 = CalcDistanceBetween2Points(x, y, x2, y2);
    double dist3 = CalcDistanceBetween2Points(x1, y1, x2, y2);
    return Math.Abs(dist3 - (dist1 + dist2)) <= allowedDistanceDifference;
}
Тон Škoda
источник
0

Вы можете сделать это, решив линейное уравнение для этого линейного сегмента с координатами точки, вы будете знать, находится ли эта точка на линии, а затем проверив границы сегмента, чтобы узнать, находится ли он внутри или снаружи. Вы можете применить некоторый порог, потому что он где-то в пространстве, скорее всего, определяется значением с плавающей запятой, и вы не должны попадать в точное значение. Пример на php

function getLineDefinition($p1=array(0,0), $p2=array(0,0)){
    
    $k = ($p1[1]-$p2[1])/($p1[0]-$p2[0]);
    $q = $p1[1]-$k*$p1[0];
    
    return array($k, $q);
    
}

function isPointOnLineSegment($line=array(array(0,0),array(0,0)), $pt=array(0,0)){
    
    // GET THE LINE DEFINITION y = k.x + q AS array(k, q) 
    $def = getLineDefinition($line[0], $line[1]);
    
    // use the line definition to find y for the x of your point
    $y = $def[0]*$pt[0]+$def[1];

    $yMin = min($line[0][1], $line[1][1]);
    $yMax = max($line[0][1], $line[1][1]);

    // exclude y values that are outside this segments bounds
    if($y>$yMax || $y<$yMin) return false;
    
    // calculate the difference of your points y value from the reference value calculated from lines definition 
    // in ideal cases this would equal 0 but we are dealing with floating point values so we need some threshold value not to lose results
    // this is up to you to fine tune
    $diff = abs($pt[1]-$y);
    
    $thr = 0.000001;
    
    return $diff<=$thr;
    
}
Саган
источник