Як можна визначити, що точка знаходиться між двома іншими точками на відрізку?


94

Скажімо, у вас є двовимірна площина, на якій 2 точки (звані a та b), представлені цілим числом x та цілим числом ay для кожної точки.

Як можна визначити, чи знаходиться інша точка c на відрізку, визначеному a та b?

Більшість я використовую python, але приклади будь-якою мовою були б корисними.


4
У цих відповідях я бачу БАГАТО довжини = sqrt (x); вони можуть працювати, але вони не швидкі. Подумайте про використання квадрата довжини; якщо ви просто порівнюєте значення довжини в квадраті між собою, то втрати точності не відбувається, і ви зберігаєте повільні дзвінки до sqrt ().
ojrac,

1
Чи точка c також представлена ​​двома цілими числами? Якщо так, то чи хочете ви знати, чи c точно знаходиться вздовж реальної прямої лінії між a і b, чи лежить на растровому наближенні прямої між a і b? Це важливе уточнення.
RobS

Подібне запитання було задано тут: stackoverflow.com/q/31346862/1914034 із рішенням, коли потрібна відстань буфера від лінії
нижче радара


1
Попередження майбутнім читачам: Значна кількість відповідей є неправильними або неповними. Кілька крайових випадків, які часто не працюють, - це горизонтальні та вертикальні лінії.
Стефнотч

Відповіді:


127

Перевірте, чи дорівнює перехресний добуток (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, щоб було зрозуміліше, які кінцеві точки сегмента, а яка точка запиту.
Крейг Гідні,

48

Ось як я це зробив:

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
Це елегантне рішення.
Пол Д. Іден,

6
Єдиною проблемою цього є чисельна стабільність - розбіжності чисел тощо дозволяють втратити точність.
Джонатан Леффлер,

26
-epsilon < (distance(a, c) + distance(c, b) - distance(a, b)) < epsilon
jfs

1
@jfs, що ти маєш на увазі під цим? Для чого потрібен чек з епсилоном?
Neon Warge

3
@NeonWarge: sqrt () повертає float. ==це неправильна річ для поплавців у більшості випадків . math.isclose()можна використовувати замість цього. У math.isclose()2008 році такого не було, і тому я надав явну нерівність epsilon( abs_tolу math.isclose()розмові).
jfs

36

Перевірте , якщо хрест продукт b-aі c-aце 0: це означає , що всі крапки лежать на одній прямій. Якщо вони є, перевірте, чи cзнаходяться координати a' між ' і b'. Використовуйте або х або у координати, до тих пір , як aі bокремий від цієї осі (або вони однакові на обох).

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

Раніше ця відповідь була безладом із трьох оновлень. Цінна інформація від них: розділ Брайана Хейса в “ Красивому коді” охоплює простір дизайну для функції тесту на колінеарність - корисне тло. Відповідь Вінсента допомогла покращити цю. І саме Хейс запропонував протестувати лише одну з координат x або y; спочатку код був andзамість if a.x != b.x else.


Оскільки перевірка дальності швидша, краще було б спершу перевірити дальність, а потім перевірити наявність колінеарної лінії в обмежувальному полі.
Grant M

1
Функція is_on (a, b, c) неправильна для випадку, коли a == b! = C. У такому випадку це поверне істину, навіть якщо 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 плаваючі, тоді перший ==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

Це тому, що є три можливості:

  • 3 точки утворюють трикутник => ac + bc> ab
  • Вони колінеарні, а c знаходиться поза сегментом ab => ac + bc> ab
  • Вони колінеарні, а c знаходиться всередині сегмента ab => ac + bc = ab

Як зазначає Джонатан Леффлер в іншому коментарі, це має числові проблеми, яких інші підходи, такі як перехресний продукт, уникають. Розділ, на який я посилаюся у своїй відповіді, пояснює.
Дарій Бекон,

3

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

Правильним рішенням є використання лінійного алгоритму Брезенхама між двома точками та перевірка, чи є третя точка однією з точок на прямій. Якщо точки є достатньо віддаленими, що обчислення алгоритму є неефективним (а для цього має бути дійсно великим), я впевнений, що ви могли б копатись і знаходити оптимізації.


Він вирішує, як провести лінію через двовимірний цілий простір між двома довільними точками та її математично правильно. Якщо третя точка є однією з точок на цій прямій, то вона, за визначенням, знаходиться між цими двома точками.
cletus

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 (виріб - lengthca * lengthba) <epsilon?
Джонатан Леффлер,

Чи не варто замість цього порівнювати квадратні довжини? Слід уникати квадратних коренів. Крім того, якщо цього неможливо уникнути через переповнення, ви можете використовувати math.hypot замість math.sqrt (із відповідною зміною аргументів).
Дарій Бекон,

Мені теж цікаво про цей епсилон. Ви можете пояснити це? Звичайно, якщо нам доводиться мати справу з поплавками, ми повинні бути обережними щодо порівнянь, але мені незрозуміло, чому епсилон робить саме це порівняння більш точним.
Дарій Бекон,

Я погоджуюсь. На це питання є кілька хороших відповідей, і це чудово. Але цей код потрібно змінити, щоб не використовувати sqrt, і останнє порівняння виправити.
Cyrille Ka

@ Джонатан: справді код є більш звичним та елегантним, використовуючи 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

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()

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
користувач43968

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

Версія відповіді Жуля на C #:

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;
}

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;
    
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.