Як сказати, чи точка в правій чи лівій частині лінії


130

У мене є безліч балів. Я хочу розділити їх на 2 різних набори. Для цього я вибираю дві точки ( a і b ) і малюю уявну лінію між ними. Тепер я хочу, щоб усі точки, що залишилися від цього рядка, були в одному наборі, а ті, що знаходяться праворуч від цього рядка, в іншому множині.

Як я можу визначити для будь-якої точки z, чи вона знаходиться в лівій чи правій множині? Я спробував обчислити кут між azb - кути менші за 180 знаходяться в правій частині, а більша за 180 - ліва, але через визначення ArcCos обчислені кути завжди менші за 180 °. Чи є формула для обчислення кутів більше 180 ° (або будь-яка інша формула, яку ви обрали праву чи ліву сторону)?


Як визначається правий чи лівий? А) з точки зору перегляду від P1 до P2 або B) ліворуч або праворуч від лінії в площині.
phkahler

2
Щоб уточнити, до другої частини вашого питання ви можете використовувати atan2 () замість acos () для обчислення правильного кута. Однак використання перехресного продукту є найкращим рішенням для цього, як зазначив Ерік Бейнвілл.
dioyziz

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

Відповіді:


202

Скористайтеся знаком визначника векторів (AB,AM), де M(X,Y)точка запиту:

position = sign((Bx - Ax) * (Y - Ay) - (By - Ay) * (X - Ax))

Це і 0на лінії, і +1на одній стороні, -1на іншій стороні.


10
+1 приємно, але слід пам’ятати про одне: помилка округлення може викликати занепокоєння, коли точка майже на лінії. Це не проблема для більшості застосувань, але час від часу кусає людей.
Стівен Канон

16
Якщо ви опинитесь у ситуації, коли помилка округлення на цьому тесті викликає у вас проблеми, ви захочете переглянути "Швидкі надійні предикати для обчислювальної геометрії" Джона Шевчука.
Стівен Канон

14
Для уточнення це те саме, що Z-компонент перехресного добутку між лінією (ba) та вектором до точки від a (ma). У вашому улюбленому векторному класі: position = sign ((ba) .cross (ma) [2])
larsmoa

3
не міняючи місцями A & B тримати ту саму лінію, але змінити знак positions?
Jayen

6
Так. A, B визначає орієнтацію, як в "ліворуч, коли стоїть біля A і дивиться на B".
Ерік Бейнвіль

224

Спробуйте цей код, який використовує перехресний продукт :

public bool isLeft(Point a, Point b, Point c){
     return ((b.X - a.X)*(c.Y - a.Y) - (b.Y - a.Y)*(c.X - a.X)) > 0;
}

Де а = пряма точка 1; b = пряма точка 2; c = бал, щоб перевірити.

Якщо формула дорівнює 0, точки є колінними.

Якщо лінія горизонтальна, то це повертає істину, якщо точка знаходиться над лінією.


6
Якщо лінія вертикальна, то?
Tofeeq Ahmad

9
ти маєш на увазі крапковий продукт?
Байян Хуанг

13
@lzprgmr: Ні, це поперечний добуток, рівнозначно детермінант 2D-матриці. Розглянемо 2D-матрицю, визначену рядками (a, b) та (c, d). Визначальним фактором є ad - bc. Вищевказана форма перетворює лінію, представлену двома точками, в один вектор, (a, b), а потім визначає інший вектор за допомогою PointA і PointC для отримання (c, d): (a, b) = (PointB.x - PointA.x, PointB.y - PointA.y) (c, d) = (PointC.x - PointA.x, PointC.y - PointA.y) Отже, визначальним фактором є такий самий, як його зазначено у публікації.
AndyG

6
Я думаю, що плутанина в тому, чи є це поперечним продуктом або крапковим продуктом, тому що це у двох вимірах. Це є крос продукт, в двох вимірах: mathworld.wolfram.com/CrossProduct.html
brianmearns

4
Для чого це варто, це можна трохи спростити return (b.x - a.x)*(c.y - a.y) > (b.y - a.y)*(c.x - a.x);, але компілятор, ймовірно, оптимізує це все одно.
Nicu Stiurca

44

Ви дивитесь на ознаку визначника

| x2-x1  x3-x1 |
| y2-y1  y3-y1 |

Це буде позитивним для очок з одного боку, а негативним - з іншого (і нулем для очок на самій прямій).


1
Розширюючи цю відповідь, якщо люди не знають, як виглядає перехресний продукт. Наступним візуальним кроком є ​​((x2-x1) * (y3-y1)) - ((y2 - y1) * (x3-x1))
Franky Rivera

10

Вектор (y1 - y2, x2 - x1)перпендикулярний до прямої і завжди вказує праворуч (або завжди вказує вліво, якщо ви орієнтація площини відрізняється від моєї).

Потім можна обчислити точковий добуток цього вектора і (x3 - x1, y3 - y1)визначити, чи лежить точка на тій же стороні лінії, що і перпендикулярний вектор (крапковий добуток> 0), чи ні.


5

Використовуючи рівняння прямої ab , отримайте координату x на прямій тій самій y-координаті, що і точка сортування.

  • Якщо точка x> лінія x, точка знаходиться праворуч від прямої.
  • Якщо точка x <рядок x, точка знаходиться зліва від рядка.
  • Якщо точка x == лінія x, то точка знаходиться на прямій.

Це неправильно, оскільки, як ви бачите з коментаря Аагінора до першої відповіді, ми не хочемо з'ясовувати, чи крапка знаходиться зліва чи справа від ПРЯМОЇ лінії AB, тобто якщо ви стоїте на A і дивитесь назустріч B - це зліва чи справа?
dioyziz

1
@dionyziz - Так? Моя відповідь не призначає "напрямок" лінії через AB. Моя відповідь припускає, що "ліворуч" - це -x напрямок кординатної системи. Прийнята відповідь обрала для визначення вектора AB та визначення ліворуч за допомогою перехресного добутку. Початкове запитання не вказує, що означає "ліворуч".
mbeckish

3
ПРИМІТКА. Якщо ви використовуєте такий підхід (а не перехресний продукт, який був затверджений як відповідь), пам’ятайте про підводний камінь, коли лінія наближається до горизонталі. Математичні помилки збільшуються і вражають нескінченність, якщо саме горизонтальну. Рішення полягає у використанні тієї осі, яка має велику дельту між двома точками. (Або, можливо, менша дельта. Це у верхній частині моєї голови.)
ToolmakerSteve

це абсолютно те, що я шукав. я не хочу знати, чи A вище або нижче B. Я просто хочу знати, чи він лівий (від’ємний х напрямок) рядка!
Jayen

5

Спочатку перевірте, чи є у вас вертикальна лінія:

if (x2-x1) == 0
  if x3 < x2
     it's on the left
  if x3 > x2
     it's on the right
  else
     it's on the line

Потім обчисліть нахил: m = (y2-y1)/(x2-x1)

Потім створіть рівняння лінії , використовуючи точку форми укосу: y - y1 = m*(x-x1) + y1. Заради мого пояснення, спростити його форму крутизна-січних (не обов'язково в вашому алгоритмі): y = mx+b.

Тепер підключіть (x3, y3)до xі y. Ось псевдокод, який детально описує, що має статися:

if m > 0
  if y3 > m*x3 + b
    it's on the left
  else if y3 < m*x3 + b
    it's on the right
  else
    it's on the line
else if m < 0
  if y3 < m*x3 + b
    it's on the left
  if y3 > m*x3+b
    it's on the right
  else
    it's on the line
else
  horizontal line; up to you what you do

3
Помилка: обчислення нахилу недійсне для вертикальних ліній. Нескінченні речі if / else. Не впевнений, чи означає це ОП під назвою ліво / праворуч - якщо так, дивлячись на це, повернутий на 90 градусів, скоротив би цей код навпіл, оскільки "нагорі" буде праворуч чи ліворуч.
phkahler

1
Ця відповідь має кілька проблем. Вертикальні лінії викликають ділення на нуль. Гірше, що вона виходить з ладу, тому що не турбується, чи нахил лінії позитивний чи негативний.

2
@phkahler, виправлено випуск вертикальної лінії. Однозначно не провал забути один тестовий випадок, але дякую за добрі слова. "Нескінченний, якщо / інше" - пояснення математичної теорії; нічого в питанні ОП не згадується про програмування. @woodchips виправили випуск вертикальної лінії. Нахил - змінна m; Я перевіряю, коли вона є позитивною чи негативною.
максим

5

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

Код: ПРИМІТКА: nearlyEqual(double,double)повертає істину, якщо два числа дуже близькі.

/*
 * @return integer code for which side of the line ab c is on.  1 means
 * left turn, -1 means right turn.  Returns
 * 0 if all three are on a line
 */
public static int findSide(
        double ax, double ay, 
        double bx, double by,
        double cx, double cy) {
    if (nearlyEqual(bx-ax,0)) { // vertical line
        if (cx < bx) {
            return by > ay ? 1 : -1;
        }
        if (cx > bx) {
            return by > ay ? -1 : 1;
        } 
        return 0;
    }
    if (nearlyEqual(by-ay,0)) { // horizontal line
        if (cy < by) {
            return bx > ax ? -1 : 1;
        }
        if (cy > by) {
            return bx > ax ? 1 : -1;
        } 
        return 0;
    }
    double slope = (by - ay) / (bx - ax);
    double yIntercept = ay - ax * slope;
    double cSolution = (slope*cx) + yIntercept;
    if (slope != 0) {
        if (cy > cSolution) {
            return bx > ax ? 1 : -1;
        }
        if (cy < cSolution) {
            return bx > ax ? -1 : 1;
        }
        return 0;
    }
    return 0;
}

Ось блок-тест:

@Test public void testFindSide() {
    assertTrue("1", 1 == Utility.findSide(1, 0, 0, 0, -1, -1));
    assertTrue("1.1", 1 == Utility.findSide(25, 0, 0, 0, -1, -14));
    assertTrue("1.2", 1 == Utility.findSide(25, 20, 0, 20, -1, 6));
    assertTrue("1.3", 1 == Utility.findSide(24, 20, -1, 20, -2, 6));

    assertTrue("-1", -1 == Utility.findSide(1, 0, 0, 0, 1, 1));
    assertTrue("-1.1", -1 == Utility.findSide(12, 0, 0, 0, 2, 1));
    assertTrue("-1.2", -1 == Utility.findSide(-25, 0, 0, 0, -1, -14));
    assertTrue("-1.3", -1 == Utility.findSide(1, 0.5, 0, 0, 1, 1));

    assertTrue("2.1", -1 == Utility.findSide(0,5, 1,10, 10,20));
    assertTrue("2.2", 1 == Utility.findSide(0,9.1, 1,10, 10,20));
    assertTrue("2.3", -1 == Utility.findSide(0,5, 1,10, 20,10));
    assertTrue("2.4", -1 == Utility.findSide(0,9.1, 1,10, 20,10));

    assertTrue("vertical 1", 1 == Utility.findSide(1,1, 1,10, 0,0));
    assertTrue("vertical 2", -1 == Utility.findSide(1,10, 1,1, 0,0));
    assertTrue("vertical 3", -1 == Utility.findSide(1,1, 1,10, 5,0));
    assertTrue("vertical 3", 1 == Utility.findSide(1,10, 1,1, 5,0));

    assertTrue("horizontal 1", 1 == Utility.findSide(1,-1, 10,-1, 0,0));
    assertTrue("horizontal 2", -1 == Utility.findSide(10,-1, 1,-1, 0,0));
    assertTrue("horizontal 3", -1 == Utility.findSide(1,-1, 10,-1, 0,-9));
    assertTrue("horizontal 4", 1 == Utility.findSide(10,-1, 1,-1, 0,-9));

    assertTrue("positive slope 1", 1 == Utility.findSide(0,0, 10,10, 1,2));
    assertTrue("positive slope 2", -1 == Utility.findSide(10,10, 0,0, 1,2));
    assertTrue("positive slope 3", -1 == Utility.findSide(0,0, 10,10, 1,0));
    assertTrue("positive slope 4", 1 == Utility.findSide(10,10, 0,0, 1,0));

    assertTrue("negative slope 1", -1 == Utility.findSide(0,0, -10,10, 1,2));
    assertTrue("negative slope 2", -1 == Utility.findSide(0,0, -10,10, 1,2));
    assertTrue("negative slope 3", 1 == Utility.findSide(0,0, -10,10, -1,-2));
    assertTrue("negative slope 4", -1 == Utility.findSide(-10,10, 0,0, -1,-2));

    assertTrue("0", 0 == Utility.findSide(1, 0, 0, 0, -1, 0));
    assertTrue("1", 0 == Utility.findSide(0,0, 0, 0, 0, 0));
    assertTrue("2", 0 == Utility.findSide(0,0, 0,1, 0,2));
    assertTrue("3", 0 == Utility.findSide(0,0, 2,0, 1,0));
    assertTrue("4", 0 == Utility.findSide(1, -2, 0, 0, -1, 2));
}

2

Якщо припустити, що точки (Ax, Ay) (Bx, By) та (Cx, Cy), потрібно обчислити:

(Bx - Axe) * (Cy - Ay) - (By - Ay) * (Cx - Axe)

Це буде дорівнює нулю, якщо точка С знаходиться на прямій, утвореній точками А і В, і матиме різний знак залежно від сторони. З якої сторони це залежить від орієнтації координат (x, y), але ви можете підключити тестові значення A, B і C до цієї формули, щоб визначити, чи є негативні значення ліворуч або праворуч.


2

Я хотів запропонувати рішення, натхнене фізикою.

Уявіть силу, прикладену уздовж лінії, і ви вимірюєте крутний момент сили щодо точки. Якщо крутний момент позитивний (проти годинникової стрілки), то точка знаходиться вліво від лінійки, але якщо крутний момент від'ємний, то точка - "справа" лінії.

Отже, якщо вектор сили дорівнює проміжку двох точок, що визначають пряму

fx = x_2 - x_1
fy = y_2 - y_1

ви перевіряєте на бік точки (px,py)на основі ознаки наступного тесту

var torque = fx*(py-y_1)-fy*(px-x_1)
if  torque>0  then
     "point on left side"
else if torque <0 then
     "point on right side"  
else
     "point on line"
end if

1

в основному, я думаю, що існує рішення, яке набагато простіше і прямо вперед, для будь-якого даного многокутника, скажімо, складається з чотирьох вершин (p1, p2, p3, p4), знайти дві крайні протилежні вершини в полігоні, в іншому слова, знайдіть, наприклад, саму верхню ліву вершину (дозвольте сказати p1) і протилежну вершину, яка розташована в самому нижньому правому куті (давайте сказати). Отже, враховуючи тестову точку C (x, y), тепер вам потрібно зробити подвійну перевірку між C і p1 і C і p4:

якщо cx> p1x AND cy> p1y ==> означає, що C нижній і праворуч від p1 наступний, якщо cx <p2x AND cy <p2y ==> означає, що C верхній і лівий від p4

висновок, C знаходиться всередині прямокутника.

Дякую :)


1
(1) Відповідає на інше запитання, ніж було задано? Звучить як тест "обмежувальний ящик", коли прямокутник вирівняний з обома осями. (2) Більш детально: робить припущення про можливі співвідношення між 4 балами. Наприклад, візьміть прямокутник і оберніть його на 45 градусів, щоб у вас вийшов ромб. У цьому діаманті немає такого поняття, як "верхньо-ліва точка". Найменша ліва точка не є самою верхньою або нижньою. І звичайно, 4 бали можуть утворювати навіть більш чужі форми. Наприклад, 3 бали можуть бути далеко в одному напрямку, а 4-й - в іншому. Продовжуй пробувати!
ToolmakerSteve

1

@ Відповідь AVB в рубіні

det = Matrix[
  [(x2 - x1), (x3 - x1)],
  [(y2 - y1), (y3 - y1)]
].determinant

Якщо detпозитивний, його вище, якщо негативний - нижче. Якщо 0, то його на лінії.


1

Ось версія, знову використовуючи крос-логіку продукту, написану в Clojure.

(defn is-left? [line point]
  (let [[[x1 y1] [x2 y2]] (sort line)
        [x-pt y-pt] point]
    (> (* (- x2 x1) (- y-pt y1)) (* (- y2 y1) (- x-pt x1)))))

Приклад використання:

(is-left? [[-3 -1] [3 1]] [0 10])
true

Що означає, що точка (0, 10) знаходиться зліва від прямої, визначеної (-3, -1) та (3, 1).

ПРИМІТКА. Ця реалізація вирішує проблему, яку ніхто з інших (поки що) не робить! Упорядкуйте питання при наданні точок, які визначають лінію. Тобто це "спрямована лінія", в певному сенсі. Тож із наведеним вище кодом ця виклик також створює результат true:

(is-left? [[3 1] [-3 -1]] [0 10])
true

Це через цей фрагмент коду:

(sort line)

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

(is-left? [[1 1] [3 1]] [10 1])
false

0

Альтернативний спосіб відчути рішення, що надаються мережами, - це зрозуміти невеликі наслідки геометрії.

Нехай pqr = [P, Q, R] - точки, що утворюють площину, яка ділиться на 2 сторони по рядку [P, R] . Ми повинні з'ясувати, чи дві точки на площині pqr , A, B, знаходяться на одній стороні.

Будь-яку точку T на площині pqr можна представити двома векторами: v = PQ і u = RQ, як:

T '= TQ = i * v + j * u

Тепер наслідки геометрії:

  1. i + j = 1: T на прямій лінії
  2. i + j <1: T на пл
  3. i + j> 1: T на Snq
  4. i + j = 0: T = Q
  5. i + j <0: T на Sq та поза Q.

i+j: <0 0 <1 =1 >1 ---------Q------[PR]--------- <== this is PQR plane ^ pr line

В загальному,

  • i + j - міра того, наскільки T знаходиться далеко від Q або лінії [P, R] і
  • знак i + j-1 позначає бічну сторону Т.

Інші значення геометрії i і j (не пов'язані з цим рішенням):

  • i , j - скаляри для T у новій системі координат, де v, u - нові осі, а Q - нове походження;
  • i , j можна розглядати як тягучу силу для P, R відповідно. Чим більше i , тим далі T знаходиться від R (більший потяг від P ).

Значення i, j можна отримати, розв’язавши рівняння:

i*vx + j*ux = T'x
i*vy + j*uy = T'y
i*vz + j*uz = T'z

Отже, нам дано 2 точки, A, B в площині:

A = a1 * v + a2 * u B = b1 * v + b2 * u

Якщо A, B знаходяться на одній стороні, це буде правдою:

sign(a1+a2-1) = sign(b1+b2-1)

Зауважимо, що це стосується також питання: Чи знаходяться A, B в одній стороні площини [P, Q, R] , в якій:

T = i * P + j * Q + k * R

а i + j + k = 1 означає, що T знаходиться на площині [P, Q, R], а знак i + j + k-1 передбачає його боковість. З цього ми маємо:

A = a1 * P + a2 * Q + a3 * R B = b1 * P + b2 * Q + b3 * R

і A, B знаходяться на одній стороні площини [P, Q, R], якщо

sign(a1+a2+a3-1) = sign(b1+b2+b3-1)

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