Алгоритм виявлення зіткнень ліній-сегментів кола?


197

У мене є лінія від А до В і коло, розташоване на С із радіусом Р.

Зображення

Який хороший алгоритм слід використовувати, щоб перевірити, чи перетинає лінія коло? І за якою координатою вздовж краю кіл це відбулося?


4
Хм. Одне запитання: ви говорите про нескінченну пряму через A і B або про відрізок кінцевої лінії від A до B?
Jason S

2
У цьому випадку - її кінцевий відрізок. Чи називається "лінія" чимось іншим залежно від того, чи вона кінцева чи нескінченна?
Мізіпзор

1
Чи є вимога до продуктивності? Чи повинен це бути швидкий метод?
chmike

На даний момент, ні, всі алгоритми, які я намагався, не помітно сповільнювали додаток.
Мізіпзор

13
@Mizipzor так, їх називають щось інше: рядкові сегменти . Якщо просто сказати "рядок", це мається на увазі нескінченна.
MestreLion

Відповіді:


200

Приймаючи

  1. Е - вихідна точка променя,
  2. L - кінцева точка променя,
  3. C - центр сфери, на яку ви протестуєте
  4. r - радіус цієї сфери

Обчисліть:
d = L - E (вектор напрямку променя, від початку до кінця)
f = E - C (вектор від центральної сфери до початку променя)

Тоді перетин знайдемо за допомогою
підключення:
P = E + t * d
Це параметричне рівняння:
P x = E x + td x
P y = E y + td y
в
(x - h) 2 + (y - k) 2 = r 2
(h, k) = центр кола.

Примітка. Ми спростили проблему до 2D тут, рішення, яке ми отримуємо, застосовується також у 3D

отримати:

  1. Розгорніть
    x 2 - 2xh + h 2 + y 2 - 2yk + k 2 - r 2 = 0
  2. Підключіть
    x = e x + td x
    y = e y + td y
    (e x + td x ) 2 - 2 (e x + td x ) h + h 2 + (e y + td y ) 2 - 2 (e y + td y ) k + k 2 - r 2 = 0
  3. Вибухнути
    e x 2 + 2e x td x + t 2 d x 2 - 2e x h - 2td x h + h 2 + e y 2 + 2e y td y + t 2 d y 2 - 2e y k - 2td y k + k 2 - r 2 = 0
  4. Група
    t 2 (d x 2 + d y 2 ) + 2t (e x d x + e y d y - d x h - d y k) + e x 2 + e y 2 - 2e x h - 2e y k + h 2 + k 2 - r 2 = 0
  5. Нарешті,
    t 2 (_d * _d) + 2t (_e * _d - _d * _c) + _e * _e - 2 (_e * _c) + _c * _c - r 2 = 0
    * Де _d - вектор d і * є крапковий продукт. *
  6. І тоді,
    t 2 (_d * _d) + 2t (_d * (_e - _c)) + (_e - _c) * (_e - _c) - r 2 = 0
  7. Нехай _f = _e - _c
    t 2 (_d * _d) + 2t (_d * _f) + _f * _f - r 2 = 0

Отже отримуємо:
t 2 * (d DOT d) + 2t * (f DOT d) + (f DOT f - r 2 ) = 0
Отже, розв’язуємо квадратичне рівняння:

float a = d.Dot( d ) ;
float b = 2*f.Dot( d ) ;
float c = f.Dot( f ) - r*r ;

float discriminant = b*b-4*a*c;
if( discriminant < 0 )
{
  // no intersection
}
else
{
  // ray didn't totally miss sphere,
  // so there is a solution to
  // the equation.

  discriminant = sqrt( discriminant );

  // either solution may be on or off the ray so need to test both
  // t1 is always the smaller value, because BOTH discriminant and
  // a are nonnegative.
  float t1 = (-b - discriminant)/(2*a);
  float t2 = (-b + discriminant)/(2*a);

  // 3x HIT cases:
  //          -o->             --|-->  |            |  --|->
  // Impale(t1 hit,t2 hit), Poke(t1 hit,t2>1), ExitWound(t1<0, t2 hit), 

  // 3x MISS cases:
  //       ->  o                     o ->              | -> |
  // FallShort (t1>1,t2>1), Past (t1<0,t2<0), CompletelyInside(t1<0, t2>1)

  if( t1 >= 0 && t1 <= 1 )
  {
    // t1 is the intersection, and it's closer than t2
    // (since t1 uses -b - discriminant)
    // Impale, Poke
    return true ;
  }

  // here t1 didn't intersect so we are either started
  // inside the sphere or completely past it
  if( t2 >= 0 && t2 <= 1 )
  {
    // ExitWound
    return true ;
  }

  // no intn: FallShort, Past, CompletelyInside
  return false ;
}

1
Здається, це працює, якщо я роблю пряму копію та вставлення, але я шукаю, щоб це зрозуміти. В (xh) ^ 2 + (yk) ^ 2 = r ^ 2 що таке h і k? Чи k до постійної, на якій лінія / промінь збільшується на y над x? А що т? Дивлячись на код, здається, ви припустили його 1 (тому його просто "вилучили"). Ці формули мають назву чи щось таке? Можливо, я можу детально розглянути їх на Вольфрам.
Мізіпзор

3
h і k - центр кола, проти якого ви перетинаєтесь. t - параметр рівняння прямої. У коді t1 і t2 - рішення. t1 і t2 говорять про те, «як далеко вздовж променя» сталося перехрестя.
bobobobo

1
Добре, зрозумів. Точковий добуток просто обчислюється за трьома елементами (x, y, z). Я переключу свій код на цей алгоритм.
chmike

21
P = E + t * dЩо таке t?
Дерек 朕 會 功夫

3
Не впевнений, чому, але, схоже, код не працює для випадку Impale. Це робиться, коли я додаю, якщо t1 <= 0 && t1> = -1 && t2 <= 0 && t2> = -1 як справжній стан, але тоді він також дає хибний позитив на одній стороні кінцевої лінії, коли коло знаходиться на "нескінченній" частині. Я ще не розумію математики, але копіюйте / вкладайте, будьте обережні.
Nicolas Mommaerts

142

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

Проектуйте вектор ACна AB. Проектований вектор, ADдає нову точку D.
Якщо відстань між Dі Cменша (або дорівнює), Rми маємо перетин.

Подобається це:
Зображення SchoolBoy


9
Є багато деталей, які слід врахувати: чи лежить D між AB? Чи перпендикулярна відстань С до прямої більше? Усе це включає величину вектора, тобто квадратний корінь.
ADB

15
Гарна ідея, але як же потім обчислити дві точки перетину?
Бен

4
@Spider це не має значення. Загалом, оскільки це варіант проблеми перетину сфери-лінії, стратегія Мізіпзора цілком справедлива. CDє проекцією, вона перпендикулярна за визначенням.

2
Це старе питання, але на цьому веб-сайті є хороший ресурс щодо відповідних алгоритмів: paulbourke.net/geometry/pointlineplane
Ендрю

1
Чудове пояснення цієї відповіді: scratchapixel.com/lessons/3d-basic-rendering/…
ShawnFeatherly

50

Я б використав алгоритм для обчислення відстані між точкою (центр кола) та лінією (лінія AB). Потім це можна використовувати для визначення точок перетину прямої з колом.

Скажімо, у нас є точки A, B, C. Ax і Ay - складові x і y точок A. Те саме для B і C. Скаляр R - радіус кола.

Цей алгоритм вимагає, щоб A, B і C були різними точками, а R не дорівнює 0.

Ось алгоритм

// compute the euclidean distance between A and B
LAB = sqrt( (Bx-Ax)²+(By-Ay)² )

// compute the direction vector D from A to B
Dx = (Bx-Ax)/LAB
Dy = (By-Ay)/LAB

// the equation of the line AB is x = Dx*t + Ax, y = Dy*t + Ay with 0 <= t <= LAB.

// compute the distance between the points A and E, where
// E is the point of AB closest the circle center (Cx, Cy)
t = Dx*(Cx-Ax) + Dy*(Cy-Ay)    

// compute the coordinates of the point E
Ex = t*Dx+Ax
Ey = t*Dy+Ay

// compute the euclidean distance between E and C
LEC = sqrt((Ex-Cx)²+(Ey-Cy)²)

// test if the line intersects the circle
if( LEC < R )
{
    // compute distance from t to circle intersection point
    dt = sqrt( R² - LEC²)

    // compute first intersection point
    Fx = (t-dt)*Dx + Ax
    Fy = (t-dt)*Dy + Ay

    // compute second intersection point
    Gx = (t+dt)*Dx + Ax
    Gy = (t+dt)*Dy + Ay
}

// else test if the line is tangent to circle
else if( LEC == R )
    // tangent point to circle is E

else
    // line doesn't touch circle

якщо будь-яка пряма, яка не перетинає коло, і його обидві точки p1 і p2 знаходяться всередині кола. в цьому випадку як працює ваш алгоритм ??
Прашант

1
Ви повинні перевірити t-dt і t + dt. Якщо t-dt <0, то p1 знаходиться всередині кола. Якщо t + dt> 1, то p2 знаходиться всередині кола. Це вірно, якщо LEC <R звичайно.
chmike

Дякую. Мені сподобався цей коментар у форматі pgm як пояснення, оскільки не було вживання слів "крапковий продукт", оскільки моя математика іржава. Однак t і dt не знаходяться між 0..1, тому під час зміни цього на python я змінив t, щоб його поділити на LAB ** 2. Я розумію, що перший поділ LAB полягає в проектуванні центру кола на пряму AB, а другий поділ LAB полягає в його нормалізації в діапазон 0..1. Також dt потрібно розділити на LAB, щоб він також нормалізувався. Таким чином, "якщо (t-dt> = 0,0)" перший перетин існує ", якщо (t + dt <= 1,0)" існує друге перетин. Це працювало з тестуванням.
пуншкард

2
Тому що точки перетину з колом знаходяться на «відстані» t+dtта t-dtна прямій. t- точка на прямій, найближчій до центру кола. Точки перетину з колом знаходяться на симетричній відстані від t. Точки перетину знаходяться на "відстанях" t-dtі t+dt. Я цитував відстань, бо це не евклідова відстань. Щоб отримати евклідову відстань від Aмісця t=0, ви повинні помножити значення на LAB.
chmike

1
@Matt W Ви маєте на увазі "Як визначити, чи відбувається перетин за межами кінцевих точок перерізу AB"? Подумайте лише про t як про міру відстані вздовж лінії. Точка А знаходиться на t=0. Точка B в t=LAB. Коли обидві точки перетину ( t1=t-tdі t2=t+td) мають від’ємні значення, то перетини знаходяться поза секцією (позаду точки A, що дивиться з боку секції точки). Коли t1 і t2 більше, ніж LAB, вони знаходяться і поза (на цей раз за точкою B). Перетин t1 (або t2) виникає між A і B лише тоді, коли t1 (або t2) він знаходиться між 0 і LAB.
Марконій

20

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

У вас буде невідома змінна в y = ax + c ( c буде невідомо ).
Щоб вирішити це, обчисліть її значення, коли рядок проходить через центр кола.

Тобто,
підключіть розташування центру кола до рівняння прямої та вирішіть для c.
Потім обчисліть точку перетину вихідної лінії та її нормальну.

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


2
Це було, власне, те, чого я хотів. Я хочу, щоб теорія: Google пошук алгоритму зіткнення ліній-коло виявляв лише код, наскільки я бачу.
Мізіпзор

Гаразд, c невідомо у вашому рівнянні, але що таке "a"? Інші відповіді, схоже, посилаються на цю змінну як "альфа" та "t". Хоча, це лише лінійна функція (y = kx + m), досить основна математика, тому я раптом відчуваю трохи іржавий. Хіба k також невідомо? Або ви маєте на увазі, що ми можемо вважати, що m = 0 і вирішувати k? Тоді б m (тобто с) завжди дорівнює нулю для вирішеного k?
Мізіпзор

1
О, вибачте - я використовую просте рівняння лінії з градієнтом і зміщенням (декартовим рівнянням). Я припускав, що ви зберігаєте рядок як таке рівняння - у цьому випадку ви використовуєте від’ємник градієнта для k. Якщо у вас немає рядка, який зберігається таким, ви можете обчислити k як (y2-y1) / (x2-x1)
a_m0d

1
Ми не вважаємо, що m дорівнює нулю; спочатку обчислюємо градієнт (так що рівняння прямої виглядає як приклад y = 2x + m), а потім, як тільки ми отримаємо градієнт, ми можемо вирішити для m, підключивши до центру кола для y та x .
a_m0d

1
+1 Дивовижне пояснення! Але я думаю, що це передбачає лінію, а не сегмент лінії. Отже, якщо найближча точка на цій прямій до центру кола не була між точками A і B, вона все одно буде зарахована.
Хасан

12

Інший метод використовує формулу площі трикутника ABC. Тест перетину простіший і ефективніший, ніж метод проекції, але пошук координат точки перетину вимагає більше роботи. Принаймні, це буде відкладено до потрібного моменту.

Формула для обчислення площі трикутника така: площа = bh / 2

де b - основна довжина, а h - висота. Ми вибрали відрізок АВ як базовий, щоб h - найкоротша відстань від C, центру кола, до прямої.

Оскільки площа трикутника може бути також обчислена добутом векторної крапки, ми можемо визначити h.

// compute the triangle area times 2 (area = area2/2)
area2 = abs( (Bx-Ax)*(Cy-Ay) - (Cx-Ax)(By-Ay) )

// compute the AB segment length
LAB = sqrt( (Bx-Ax)² + (By-Ay)² )

// compute the triangle height
h = area2/LAB

// if the line intersects the circle
if( h < R )
{
    ...
}        

ОНОВЛЕННЯ 1:

Ви можете оптимізувати код, використовуючи описані тут швидкі обернені обчислення квадратних коренів, щоб отримати гарне наближення до 1 / LAB.

Обчислити точку перетину не так вже й складно. Ось це іде

// compute the line AB direction vector components
Dx = (Bx-Ax)/LAB
Dy = (By-Ay)/LAB

// compute the distance from A toward B of closest point to C
t = Dx*(Cx-Ax) + Dy*(Cy-Ay)

// t should be equal to sqrt( (Cx-Ax)² + (Cy-Ay)² - h² )

// compute the intersection point distance from t
dt = sqrt( R² - h² )

// compute first intersection point coordinate
Ex = Ax + (t-dt)*Dx
Ey = Ay + (t-dt)*Dy

// compute second intersection point coordinate
Fx = Ax + (t+dt)*Dx
Fy = Ay + (t+dt)*Dy

Якщо h = R, то лінія AB дотична до кола, а значення dt = 0 і E = F. Точкові координати - це координати E і F.

Ви повинні перевірити, що A відрізняється від B, а довжина сегмента не є нульовою, якщо це може статися у вашій програмі.


2
Мені подобається простота в цьому методі. Можливо, я міг би адаптувати частину оточуючого коду, щоб не потрібна сама фактична точка зіткнення, я бачу, що станеться, якщо я використовую A або B, а не обчислену точку між ними.
Мізіпзор

1
t = Dx * (Cx-Ax) + Dy * (Cy-Ax) слід читати t = Dx * (Cx-Ax) + Dy * (Cy-Ay)
Стоунтіп

Це правильно. Дякую, що вказали на це. Я виправив це в дописі.
chmike

щойно відредагований - перший рядок обчислює площу трикутника за допомогою поперечного добутку, а не крапкового добутку. перевіряється з кодом тут: stackoverflow.com/questions/2533011 / ...
ericsoco

4
зауважимо також, що перша половина цієї відповіді перевіряє перетин з лінією, а не відрізком лінії (як задано у запитанні).
ericsoco

8

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

vector distVector = centerPoint - projectedPoint;
if(distVector.length() < circle.radius)
{
    double distance = circle.radius - distVector.length();
    vector moveVector = distVector.normalize() * distance;
    circle.move(moveVector);
}

http://jsfiddle.net/ercang/ornh3594/1/

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

vector distVector = centerPoint - startPoint;
if(distVector.length() < circle.radius)
{
    double distance = circle.radius - distVector.length();
    vector moveVector = distVector.normalize() * distance;
    circle.move(moveVector);
}

https://jsfiddle.net/ercang/menp0991/


5

Це рішення, яке я знайшов, здавалося трохи легшим, ніж деякі інші.

Приймає:

p1 and p2 as the points for the line, and
c as the center point for the circle and r for the radius

Я би вирішив для рівняння прямої у нахильній формі перехоплення. Однак мені не хотілося мати справу з важкими рівняннями cв якості точки, тому я просто перемістив систему координат так, щоб коло було на0,0

p3 = p1 - c
p4 = p2 - c

До речі, щоразу, коли я віднімаю точки один від одного, я віднімаю x'' і '', а потім віднімаю y'і' і ввожу їх у нову точку, на всякий випадок, коли хтось не знав.

У будь-якому випадку я тепер вирішую для рівняння прямої з p3та p4:

m = (p4_y - p3_y) / (p4_x - p3) (the underscore is an attempt at subscript)
y = mx + b
y - mx = b (just put in a point for x and y, and insert the m we found)

Гаразд. Тепер мені потрібно встановити ці рівняння рівними. Спершу мені потрібно розв’язати рівняння кола дляx

x^2 + y^2 = r^2
y^2 = r^2 - x^2
y = sqrt(r^2 - x^2)

Тоді я встановив їх рівними:

mx + b = sqrt(r^2 - x^2)

І розв’яжіть для квадратичного рівняння ( 0 = ax^2 + bx + c):

(mx + b)^2 = r^2 - x^2
(mx)^2 + 2mbx + b^2 = r^2 - x^2
0 = m^2 * x^2 + x^2 + 2mbx + b^2 - r^2
0 = (m^2 + 1) * x^2 + 2mbx + b^2 - r^2

Тепер у мене є мій a, bі c.

a = m^2 + 1
b = 2mb
c = b^2 - r^2

Тому я вклав це у квадратичну формулу:

(-b ± sqrt(b^2 - 4ac)) / 2a

І замінити значення на значення, а потім максимально спростити:

(-2mb ± sqrt(b^2 - 4ac)) / 2a
(-2mb ± sqrt((-2mb)^2 - 4(m^2 + 1)(b^2 - r^2))) / 2(m^2 + 1)
(-2mb ± sqrt(4m^2 * b^2 - 4(m^2 * b^2 - m^2 * r^2 + b^2 - r^2))) / 2m^2 + 2
(-2mb ± sqrt(4 * (m^2 * b^2 - (m^2 * b^2 - m^2 * r^2 + b^2 - r^2))))/ 2m^2 + 2
(-2mb ± sqrt(4 * (m^2 * b^2 - m^2 * b^2 + m^2 * r^2 - b^2 + r^2)))/ 2m^2 + 2
(-2mb ± sqrt(4 * (m^2 * r^2 - b^2 + r^2)))/ 2m^2 + 2
(-2mb ± sqrt(4) * sqrt(m^2 * r^2 - b^2 + r^2))/ 2m^2 + 2
(-2mb ± 2 * sqrt(m^2 * r^2 - b^2 + r^2))/ 2m^2 + 2
(-2mb ± 2 * sqrt(m^2 * r^2 + r^2 - b^2))/ 2m^2 + 2
(-2mb ± 2 * sqrt(r^2 * (m^2 + 1) - b^2))/ 2m^2 + 2

Це майже наскільки це спростить. Нарешті, відокремте рівняння з ±:

(-2mb + 2 * sqrt(r^2 * (m^2 + 1) - b^2))/ 2m^2 + 2 or     
(-2mb - 2 * sqrt(r^2 * (m^2 + 1) - b^2))/ 2m^2 + 2 

Потім просто підключіть результат обох цих рівнянь до xв mx + b. Для наочності я написав код JavaScript, щоб показати, як це використовувати:

function interceptOnCircle(p1,p2,c,r){
    //p1 is the first line point
    //p2 is the second line point
    //c is the circle's center
    //r is the circle's radius

    var p3 = {x:p1.x - c.x, y:p1.y - c.y} //shifted line points
    var p4 = {x:p2.x - c.x, y:p2.y - c.y}

    var m = (p4.y - p3.y) / (p4.x - p3.x); //slope of the line
    var b = p3.y - m * p3.x; //y-intercept of line

    var underRadical = Math.pow((Math.pow(r,2)*(Math.pow(m,2)+1)),2)-Math.pow(b,2)); //the value under the square root sign 

    if (underRadical < 0){
    //line completely missed
        return false;
    } else {
        var t1 = (-2*m*b+2*Math.sqrt(underRadical))/(2 * Math.pow(m,2) + 2); //one of the intercept x's
        var t2 = (-2*m*b-2*Math.sqrt(underRadical))/(2 * Math.pow(m,2) + 2); //other intercept's x
        var i1 = {x:t1,y:m*t1+b} //intercept point 1
        var i2 = {x:t2,y:m*t2+b} //intercept point 2
        return [i1,i2];
    }
}

Я сподіваюся, що це допомагає!

PS Якщо хтось виявить помилки або має якісь пропозиції, будь ласка, прокоментуйте. Я дуже новий і вітаю всю допомогу / пропозиції.


Якщо можливо, також опублікуйте деякі значення вибірки, щоб ми могли швидко зрозуміти потік.
Прабінд

З underRadicalдодатковою ')'
автор Євгена

4

Ви можете знайти точку на нескінченній лінії, найближчій до центру кола, проектуючи вектор AC на вектор AB. Обчисліть відстань між цією точкою та центром кола. Якщо більше R, то перетину немає. Якщо відстань дорівнює R, лінія є дотичною до кола, а точка, найближча до центру кола, є фактично точкою перетину. Якщо відстань менше R, то є 2 точки перетину. Вони лежать на одній відстані від точки, найближчої до центру кола. Ця відстань легко обчислити, використовуючи теорему Піфагора. Ось алгоритм у псевдокоді:

{
dX = bX - aX;
dY = bY - aY;
if ((dX == 0) && (dY == 0))
  {
  // A and B are the same points, no way to calculate intersection
  return;
  }

dl = (dX * dX + dY * dY);
t = ((cX - aX) * dX + (cY - aY) * dY) / dl;

// point on a line nearest to circle center
nearestX = aX + t * dX;
nearestY = aY + t * dY;

dist = point_dist(nearestX, nearestY, cX, cY);

if (dist == R)
  {
  // line segment touches circle; one intersection point
  iX = nearestX;
  iY = nearestY;

  if (t < 0 || t > 1)
    {
    // intersection point is not actually within line segment
    }
  }
else if (dist < R)
  {
  // two possible intersection points

  dt = sqrt(R * R - dist * dist) / sqrt(dl);

  // intersection point nearest to A
  t1 = t - dt;
  i1X = aX + t1 * dX;
  i1Y = aY + t1 * dY;
  if (t1 < 0 || t1 > 1)
    {
    // intersection point is not actually within line segment
    }

  // intersection point farthest from A
  t2 = t + dt;
  i2X = aX + t2 * dX;
  i2Y = aY + t2 * dY;
  if (t2 < 0 || t2 > 1)
    {
    // intersection point is not actually within line segment
    }
  }
else
  {
  // no intersection
  }
}

EDIT: доданий код, щоб перевірити, чи знайдені точки перетину насправді в межах сегмента лінії.


Ви пропустили один випадок, оскільки ми говоримо про відрізок рядка: коли сегмент закінчується в колі.
ADB

@ADB насправді мій алгоритм працює лише для нескінченних рядків, а не для сегментів рядків. Існує багато випадків, коли він не обробляє сегменти рядків.
Юозас Контвайніс

Первісне запитання стосувалося перетину ліній, а не перетину колії-лінії, що є набагато легшою проблемою.
msumme

4

Дивно можу відповісти, але не коментувати ... Мені сподобався підхід Multitaskpro змінити все, щоб центр кола впав на початок. На жаль, у його коді є дві проблеми. Спочатку в нижній квадратній частині потрібно зняти подвійну потужність. Так ні:

var underRadical = Math.pow((Math.pow(r,2)*(Math.pow(m,2)+1)),2)-Math.pow(b,2));

але:

var underRadical = Math.pow(r,2)*(Math.pow(m,2)+1)) - Math.pow(b,2);

У кінцевих координатах він забуває змістити рішення назад. Так ні:

var i1 = {x:t1,y:m*t1+b}

але:

var i1 = {x:t1+c.x, y:m*t1+b+c.y};

Вся функція потім стає:

function interceptOnCircle(p1, p2, c, r) {
    //p1 is the first line point
    //p2 is the second line point
    //c is the circle's center
    //r is the circle's radius

    var p3 = {x:p1.x - c.x, y:p1.y - c.y}; //shifted line points
    var p4 = {x:p2.x - c.x, y:p2.y - c.y};

    var m = (p4.y - p3.y) / (p4.x - p3.x); //slope of the line
    var b = p3.y - m * p3.x; //y-intercept of line

    var underRadical = Math.pow(r,2)*Math.pow(m,2) + Math.pow(r,2) - Math.pow(b,2); //the value under the square root sign 

    if (underRadical < 0) {
        //line completely missed
        return false;
    } else {
        var t1 = (-m*b + Math.sqrt(underRadical))/(Math.pow(m,2) + 1); //one of the intercept x's
        var t2 = (-m*b - Math.sqrt(underRadical))/(Math.pow(m,2) + 1); //other intercept's x
        var i1 = {x:t1+c.x, y:m*t1+b+c.y}; //intercept point 1
        var i2 = {x:t2+c.x, y:m*t2+b+c.y}; //intercept point 2
        return [i1, i2];
    }
}

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

Примітка. Це добре працює для рядків, але не працює для сегментів рядків.
Майк

3

Тут вам знадобиться математика:

Припустимо, A = (Xa, Ya), B = (Xb, Yb) і C = (Xc, Yc). Будь-яка точка на прямій від А до В має координати (альфа * Xa + (1-альфа) Xb, альфа Ya + (1-альфа) * ​​Yb) = P

Якщо точка P має відстань R до C, вона повинна бути на колі. Що ви хочете - це вирішити

distance(P, C) = R

це є

(alpha*Xa + (1-alpha)*Xb)^2 + (alpha*Ya + (1-alpha)*Yb)^2 = R^2
alpha^2*Xa^2 + alpha^2*Xb^2 - 2*alpha*Xb^2 + Xb^2 + alpha^2*Ya^2 + alpha^2*Yb^2 - 2*alpha*Yb^2 + Yb^2=R^2
(Xa^2 + Xb^2 + Ya^2 + Yb^2)*alpha^2 - 2*(Xb^2 + Yb^2)*alpha + (Xb^2 + Yb^2 - R^2) = 0

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


3

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

Точка зіткнення, очевидно, є найближчою точкою між лінією та сферою (яка буде обчислена під час обчислення відстані між сферою та лінією)

Відстань між точкою та лінією:
http://mathworld.wolfram.com/Point-LineDistance3-Dimensions.html


1
Це в 2D, а не в 3D; як ви кажете, це насправді не має значення
Martijn

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

2
+1 із сильним відгуком. (хоча я б пов’язав з іншим сайтом, сайт pbourke виглядає заплутаним) Усі інші відповіді поки що складні. Хоча ваш коментар "Ця точка є також точкою перетину на прямій" неправильний, немає жодної точки, яка була побудована в процесі обчислення.
Jason S


Я пояснив трохи краще про найближчу точку і пов’язаний із mathworld замість pbourke :)
Мартін

3

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

Ви можете спробувати код на цій демонстраційній версії . Код взятий з моїх алгоритмів repo .

введіть тут опис зображення

// Small epsilon value
var EPS = 0.0000001;

// point (x, y)
function Point(x, y) {
  this.x = x;
  this.y = y;
}

// Circle with center at (x,y) and radius r
function Circle(x, y, r) {
  this.x = x;
  this.y = y;
  this.r = r;
}

// A line segment (x1, y1), (x2, y2)
function LineSegment(x1, y1, x2, y2) {
  var d = Math.sqrt( (x1-x2)*(x1-x2) + (y1-y2)*(y1-y2) );
  if (d < EPS) throw 'A point is not a line segment';
  this.x1 = x1; this.y1 = y1;
  this.x2 = x2; this.y2 = y2;
}

// An infinite line defined as: ax + by = c
function Line(a, b, c) {
  this.a = a; this.b = b; this.c = c;
  // Normalize line for good measure
  if (Math.abs(b) < EPS) {
    c /= a; a = 1; b = 0;
  } else { 
    a = (Math.abs(a) < EPS) ? 0 : a / b;
    c /= b; b = 1; 
  }
}

// Given a line in standard form: ax + by = c and a circle with 
// a center at (x,y) with radius r this method finds the intersection
// of the line and the circle (if any). 
function circleLineIntersection(circle, line) {

  var a = line.a, b = line.b, c = line.c;
  var x = circle.x, y = circle.y, r = circle.r;

  // Solve for the variable x with the formulas: ax + by = c (equation of line)
  // and (x-X)^2 + (y-Y)^2 = r^2 (equation of circle where X,Y are known) and expand to obtain quadratic:
  // (a^2 + b^2)x^2 + (2abY - 2ac + - 2b^2X)x + (b^2X^2 + b^2Y^2 - 2bcY + c^2 - b^2r^2) = 0
  // Then use quadratic formula X = (-b +- sqrt(a^2 - 4ac))/2a to find the 
  // roots of the equation (if they exist) and this will tell us the intersection points

  // In general a quadratic is written as: Ax^2 + Bx + C = 0
  // (a^2 + b^2)x^2 + (2abY - 2ac + - 2b^2X)x + (b^2X^2 + b^2Y^2 - 2bcY + c^2 - b^2r^2) = 0
  var A = a*a + b*b;
  var B = 2*a*b*y - 2*a*c - 2*b*b*x;
  var C = b*b*x*x + b*b*y*y - 2*b*c*y + c*c - b*b*r*r;

  // Use quadratic formula x = (-b +- sqrt(a^2 - 4ac))/2a to find the 
  // roots of the equation (if they exist).

  var D = B*B - 4*A*C;
  var x1,y1,x2,y2;

  // Handle vertical line case with b = 0
  if (Math.abs(b) < EPS) {

    // Line equation is ax + by = c, but b = 0, so x = c/a
    x1 = c/a;

    // No intersection
    if (Math.abs(x-x1) > r) return [];

    // Vertical line is tangent to circle
    if (Math.abs((x1-r)-x) < EPS || Math.abs((x1+r)-x) < EPS)
      return [new Point(x1, y)];

    var dx = Math.abs(x1 - x);
    var dy = Math.sqrt(r*r-dx*dx);

    // Vertical line cuts through circle
    return [
      new Point(x1,y+dy),
      new Point(x1,y-dy)
    ];

  // Line is tangent to circle
  } else if (Math.abs(D) < EPS) {

    x1 = -B/(2*A);
    y1 = (c - a*x1)/b;

    return [new Point(x1,y1)];

  // No intersection
  } else if (D < 0) {

    return [];

  } else {

    D = Math.sqrt(D);

    x1 = (-B+D)/(2*A);
    y1 = (c - a*x1)/b;

    x2 = (-B-D)/(2*A);
    y2 = (c - a*x2)/b;

    return [
      new Point(x1, y1),
      new Point(x2, y2)
    ];

  }

}

// Converts a line segment to a line in general form
function segmentToGeneralForm(x1,y1,x2,y2) {
  var a = y1 - y2;
  var b = x2 - x1;
  var c = x2*y1 - x1*y2;
  return new Line(a,b,c);
}

// Checks if a point 'pt' is inside the rect defined by (x1,y1), (x2,y2)
function pointInRectangle(pt,x1,y1,x2,y2) {
  var x = Math.min(x1,x2), X = Math.max(x1,x2);
  var y = Math.min(y1,y2), Y = Math.max(y1,y2);
  return x - EPS <= pt.x && pt.x <= X + EPS &&
         y - EPS <= pt.y && pt.y <= Y + EPS;
}

// Finds the intersection(s) of a line segment and a circle
function lineSegmentCircleIntersection(segment, circle) {

  var x1 = segment.x1, y1 = segment.y1, x2 = segment.x2, y2 = segment.y2;
  var line = segmentToGeneralForm(x1,y1,x2,y2);
  var pts = circleLineIntersection(circle, line);

  // No intersection
  if (pts.length === 0) return [];

  var pt1 = pts[0];
  var includePt1 = pointInRectangle(pt1,x1,y1,x2,y2);

  // Check for unique intersection
  if (pts.length === 1) {
    if (includePt1) return [pt1];
    return [];
  }

  var pt2 = pts[1];
  var includePt2 = pointInRectangle(pt2,x1,y1,x2,y2);

  // Check for remaining intersections
  if (includePt1 && includePt2) return [pt1, pt2];
  if (includePt1) return [pt1];
  if (includePt2) return [pt2];
  return [];

}

3

У цьому повідомленні зіткнення лінії кола буде перевірено шляхом перевірки відстані між центром кола та точкою на відрізку лінії (Ipoint), які представляють точку перетину між нормальним N (зображення 2) від центру кола до відрізка лінії.

( https://i.stack.imgur.com/3o6do.png )Зображення 1. Пошук векторів E і D

На зображенні 1 зображено одне коло та одна лінія, вектор Початкова точка точка до лінії, векторна точка B до кінцевої точки лінії, вектор C - точка до центру кола. Тепер ми повинні знайти вектор E (від початкової точки лінії до центру кола) та вектор D (від початкової точки лінії до кінцевої точки лінії). Цей розрахунок показано на зображенні 1.

( https://i.stack.imgur.com/7098a.png )Зображення 2. Пошук вектора X

На зображенні 2 ми бачимо, що вектор E проектується на вектор D "крапковий добуток" вектора E та одиничного вектора D, результат крапкового добутку - скалярний Xp, що представляє відстань між початковою точкою лінії та точкою перетину (точка) вектор N і вектор D. Наступний вектор X знаходимо шляхом множення одиничного вектора D і скалярного Xp.

Тепер нам потрібно знайти вектор Z (вектор до Ipoint), його легко просте векторне додавання вектора A (початкова точка на прямій) та вектора X. Далі нам потрібно розібратися з особливими випадками, які ми повинні перевірити, чи є Ipoint на відрізку лінії, якщо його ми не повинні з'ясувати, ліворуч від нього або праворуч від нього, ми будемо використовувати вектор, найближчий до того, щоб визначити, яка точка є найближчою до кола.

( https://i.stack.imgur.com/p9WIr.png )Зображення 3. Пошук найближчої точки

Коли проекція Xp від'ємна, точка ліворуч від лінійного відрізка, вектор найближчий дорівнює вектору початкової точки лінії, коли проекція Xp більша за величину вектора D, тоді Ipoint - справа від відрізка лінії, тоді найближчий вектор дорівнює вектору кінця лінії точка в будь-якому іншому випадку найближчий вектор дорівнює вектору Z.

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

( https://i.stack.imgur.com/QJ63q.png )Зображення 4. Перевірка на зіткнення

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


2

Якщо координати лінії Ax, Ay і Bx, By, а центр кіл - Cx, Cy, то формули рядків:

x = Ax * t + Bx * (1 - t)

y = Ay * t + Від * (1 - t)

де 0 <= t <= 1

і коло є

(Cx - x) ^ 2 + (Cy - y) ^ 2 = R ^ 2

якщо підставити формули x і y рядка у формулу кіл, ви отримаєте рівняння t другого порядку t і його розв'язки - точки перетину (якщо такі є). Якщо ви отримаєте значення, яке менше 0 або більше 1, то це не є рішенням, але це показує, що лінія "вказує" на напрямок кола.


2

Просто доповнення до цієї теми ... Нижче наведена версія коду, розміщена pahlevan, але для C # / XNA і трохи прибрана:

    /// <summary>
    /// Intersects a line and a circle.
    /// </summary>
    /// <param name="location">the location of the circle</param>
    /// <param name="radius">the radius of the circle</param>
    /// <param name="lineFrom">the starting point of the line</param>
    /// <param name="lineTo">the ending point of the line</param>
    /// <returns>true if the line and circle intersect each other</returns>
    public static bool IntersectLineCircle(Vector2 location, float radius, Vector2 lineFrom, Vector2 lineTo)
    {
        float ab2, acab, h2;
        Vector2 ac = location - lineFrom;
        Vector2 ab = lineTo - lineFrom;
        Vector2.Dot(ref ab, ref ab, out ab2);
        Vector2.Dot(ref ac, ref ab, out acab);
        float t = acab / ab2;

        if (t < 0)
            t = 0;
        else if (t > 1)
            t = 1;

        Vector2 h = ((ab * t) + lineFrom) - location;
        Vector2.Dot(ref h, ref h, out h2);

        return (h2 <= (radius * radius));
    }

В C # / XNA ви можете використовуватиRay.Intersects(BoundingSphere)
bobobobo

2

введіть тут опис зображення

' VB.NET - Code

Function CheckLineSegmentCircleIntersection(x1 As Double, y1 As Double, x2 As Double, y2 As Double, xc As Double, yc As Double, r As Double) As Boolean
    Static xd As Double = 0.0F
    Static yd As Double = 0.0F
    Static t As Double = 0.0F
    Static d As Double = 0.0F
    Static dx_2_1 As Double = 0.0F
    Static dy_2_1 As Double = 0.0F

    dx_2_1 = x2 - x1
    dy_2_1 = y2 - y1

    t = ((yc - y1) * dy_2_1 + (xc - x1) * dx_2_1) / (dy_2_1 * dy_2_1 + dx_2_1 * dx_2_1)

    If 0 <= t And t <= 1 Then
        xd = x1 + t * dx_2_1
        yd = y1 + t * dy_2_1

        d = Math.Sqrt((xd - xc) * (xd - xc) + (yd - yc) * (yd - yc))
        Return d <= r
    Else
        d = Math.Sqrt((xc - x1) * (xc - x1) + (yc - y1) * (yc - y1))
        If d <= r Then
            Return True
        Else
            d = Math.Sqrt((xc - x2) * (xc - x2) + (yc - y2) * (yc - y2))
            If d <= r Then
                Return True
            Else
                Return False
            End If
        End If
    End If
End Function

2

Я створив цю функцію для iOS, відповідаючи відповіді chmike

+ (NSArray *)intersectionPointsOfCircleWithCenter:(CGPoint)center withRadius:(float)radius toLinePoint1:(CGPoint)p1 andLinePoint2:(CGPoint)p2
{
    NSMutableArray *intersectionPoints = [NSMutableArray array];

    float Ax = p1.x;
    float Ay = p1.y;
    float Bx = p2.x;
    float By = p2.y;
    float Cx = center.x;
    float Cy = center.y;
    float R = radius;


    // compute the euclidean distance between A and B
    float LAB = sqrt( pow(Bx-Ax, 2)+pow(By-Ay, 2) );

    // compute the direction vector D from A to B
    float Dx = (Bx-Ax)/LAB;
    float Dy = (By-Ay)/LAB;

    // Now the line equation is x = Dx*t + Ax, y = Dy*t + Ay with 0 <= t <= 1.

    // compute the value t of the closest point to the circle center (Cx, Cy)
    float t = Dx*(Cx-Ax) + Dy*(Cy-Ay);

    // This is the projection of C on the line from A to B.

    // compute the coordinates of the point E on line and closest to C
    float Ex = t*Dx+Ax;
    float Ey = t*Dy+Ay;

    // compute the euclidean distance from E to C
    float LEC = sqrt( pow(Ex-Cx, 2)+ pow(Ey-Cy, 2) );

    // test if the line intersects the circle
    if( LEC < R )
    {
        // compute distance from t to circle intersection point
        float dt = sqrt( pow(R, 2) - pow(LEC,2) );

        // compute first intersection point
        float Fx = (t-dt)*Dx + Ax;
        float Fy = (t-dt)*Dy + Ay;

        // compute second intersection point
        float Gx = (t+dt)*Dx + Ax;
        float Gy = (t+dt)*Dy + Ay;

        [intersectionPoints addObject:[NSValue valueWithCGPoint:CGPointMake(Fx, Fy)]];
        [intersectionPoints addObject:[NSValue valueWithCGPoint:CGPointMake(Gx, Gy)]];
    }

    // else test if the line is tangent to circle
    else if( LEC == R ) {
        // tangent point to circle is E
        [intersectionPoints addObject:[NSValue valueWithCGPoint:CGPointMake(Ex, Ey)]];
    }
    else {
        // line doesn't touch circle
    }

    return intersectionPoints;
}

2

Ще один в c # (частковий клас Circle). Випробуваний і працює як шарм.

public class Circle : IEquatable<Circle>
{
    // ******************************************************************
    // The center of a circle
    private Point _center;
    // The radius of a circle
    private double _radius;

   // ******************************************************************
    /// <summary>
    /// Find all intersections (0, 1, 2) of the circle with a line defined by its 2 points.
    /// Using: http://math.stackexchange.com/questions/228841/how-do-i-calculate-the-intersections-of-a-straight-line-and-a-circle
    /// Note: p is the Center.X and q is Center.Y
    /// </summary>
    /// <param name="linePoint1"></param>
    /// <param name="linePoint2"></param>
    /// <returns></returns>
    public List<Point> GetIntersections(Point linePoint1, Point linePoint2)
    {
        List<Point> intersections = new List<Point>();

        double dx = linePoint2.X - linePoint1.X;

        if (dx.AboutEquals(0)) // Straight vertical line
        {
            if (linePoint1.X.AboutEquals(Center.X - Radius) || linePoint1.X.AboutEquals(Center.X + Radius))
            {
                Point pt = new Point(linePoint1.X, Center.Y);
                intersections.Add(pt);
            }
            else if (linePoint1.X > Center.X - Radius && linePoint1.X < Center.X + Radius)
            {
                double x = linePoint1.X - Center.X;

                Point pt = new Point(linePoint1.X, Center.Y + Math.Sqrt(Radius * Radius - (x * x)));
                intersections.Add(pt);

                pt = new Point(linePoint1.X, Center.Y - Math.Sqrt(Radius * Radius - (x * x)));
                intersections.Add(pt);
            }

            return intersections;
        }

        // Line function (y = mx + b)
        double dy = linePoint2.Y - linePoint1.Y;
        double m = dy / dx;
        double b = linePoint1.Y - m * linePoint1.X;

        double A = m * m + 1;
        double B = 2 * (m * b - m * _center.Y - Center.X);
        double C = Center.X * Center.X + Center.Y * Center.Y - Radius * Radius - 2 * b * Center.Y + b * b;

        double discriminant = B * B - 4 * A * C;

        if (discriminant < 0)
        {
            return intersections; // there is no intersections
        }

        if (discriminant.AboutEquals(0)) // Tangeante (touch on 1 point only)
        {
            double x = -B / (2 * A);
            double y = m * x + b;

            intersections.Add(new Point(x, y));
        }
        else // Secant (touch on 2 points)
        {
            double x = (-B + Math.Sqrt(discriminant)) / (2 * A);
            double y = m * x + b;
            intersections.Add(new Point(x, y));

            x = (-B - Math.Sqrt(discriminant)) / (2 * A);
            y = m * x + b;
            intersections.Add(new Point(x, y));
        }

        return intersections;
    }

    // ******************************************************************
    // Get the center
    [XmlElement("Center")]
    public Point Center
    {
        get { return _center; }
        set
        {
            _center = value;
        }
    }

    // ******************************************************************
    // Get the radius
    [XmlElement]
    public double Radius
    {
        get { return _radius; }
        set { _radius = value; }
    }

    //// ******************************************************************
    //[XmlArrayItemAttribute("DoublePoint")]
    //public List<Point> Coordinates
    //{
    //    get { return _coordinates; }
    //}

    // ******************************************************************
    // Construct a circle without any specification
    public Circle()
    {
        _center.X = 0;
        _center.Y = 0;
        _radius = 0;
    }

    // ******************************************************************
    // Construct a circle without any specification
    public Circle(double radius)
    {
        _center.X = 0;
        _center.Y = 0;
        _radius = radius;
    }

    // ******************************************************************
    // Construct a circle with the specified circle
    public Circle(Circle circle)
    {
        _center = circle._center;
        _radius = circle._radius;
    }

    // ******************************************************************
    // Construct a circle with the specified center and radius
    public Circle(Point center, double radius)
    {
        _center = center;
        _radius = radius;
    }

    // ******************************************************************
    // Construct a circle based on one point
    public Circle(Point center)
    {
        _center = center;
        _radius = 0;
    }

    // ******************************************************************
    // Construct a circle based on two points
    public Circle(Point p1, Point p2)
    {
        Circle2Points(p1, p2);
    }

Вимагається:

using System;

namespace Mathematic
{
    public static class DoubleExtension
    {
        // ******************************************************************
        // Base on Hans Passant Answer on:
        // http://stackoverflow.com/questions/2411392/double-epsilon-for-equality-greater-than-less-than-less-than-or-equal-to-gre

        /// <summary>
        /// Compare two double taking in account the double precision potential error.
        /// Take care: truncation errors accumulate on calculation. More you do, more you should increase the epsilon.
        public static bool AboutEquals(this double value1, double value2)
        {
            if (double.IsPositiveInfinity(value1))
                return double.IsPositiveInfinity(value2);

            if (double.IsNegativeInfinity(value1))
                return double.IsNegativeInfinity(value2);

            if (double.IsNaN(value1))
                return double.IsNaN(value2);

            double epsilon = Math.Max(Math.Abs(value1), Math.Abs(value2)) * 1E-15;
            return Math.Abs(value1 - value2) <= epsilon;
        }

        // ******************************************************************
        // Base on Hans Passant Answer on:
        // http://stackoverflow.com/questions/2411392/double-epsilon-for-equality-greater-than-less-than-less-than-or-equal-to-gre

        /// <summary>
        /// Compare two double taking in account the double precision potential error.
        /// Take care: truncation errors accumulate on calculation. More you do, more you should increase the epsilon.
        /// You get really better performance when you can determine the contextual epsilon first.
        /// </summary>
        /// <param name="value1"></param>
        /// <param name="value2"></param>
        /// <param name="precalculatedContextualEpsilon"></param>
        /// <returns></returns>
        public static bool AboutEquals(this double value1, double value2, double precalculatedContextualEpsilon)
        {
            if (double.IsPositiveInfinity(value1))
                return double.IsPositiveInfinity(value2);

            if (double.IsNegativeInfinity(value1))
                return double.IsNegativeInfinity(value2);

            if (double.IsNaN(value1))
                return double.IsNaN(value2);

            return Math.Abs(value1 - value2) <= precalculatedContextualEpsilon;
        }

        // ******************************************************************
        public static double GetContextualEpsilon(this double biggestPossibleContextualValue)
        {
            return biggestPossibleContextualValue * 1E-15;
        }

        // ******************************************************************
        /// <summary>
        /// Mathlab equivalent
        /// </summary>
        /// <param name="dividend"></param>
        /// <param name="divisor"></param>
        /// <returns></returns>
        public static double Mod(this double dividend, double divisor)
        {
            return dividend - System.Math.Floor(dividend / divisor) * divisor;
        }

        // ******************************************************************
    }
}

2

Ось гарне рішення в JavaScript (з усією необхідною математикою та живою ілюстрацією) https://bl.ocks.org/milkbread/11000965

Хоча is_onфункція в цьому рішенні потребує модифікацій:

function is_on(a, b, c) {
    return Math.abs(distance(a,c) + distance(c,b) - distance(a,b))<0.000001;
}


2

Коло справді поганий хлопець :) Тож хороший спосіб - уникнути справжнього кола, якщо можете. Якщо ви робите перевірку на зіткнення щодо ігор, ви можете виконати деякі спрощення і мати лише 3 крапки та кілька порівнянь.

Я називаю це «жировою точкою» або «тонким колом». його вид еліпса з нульовим радіусом у напрямку, паралельному відрізку. але повний радіус у напрямку, перпендикулярному відрізку

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

s0s1 = B-A;
s0qp = C-A;
rSqr = r*r;

По-друге, індекс h в hvec2f означає, ніж вектор, повинен сприяти хоризонтальним операціям, як точка () / det (). Це означає, що його компоненти повинні розміщуватися в окремих xmm-регістрах, щоб уникнути перетасування / hadd'ing / hsub'ing. І ось ми з найефективнішою версією найпростішого виявлення зіткнень для 2D гри:

bool fat_point_collides_segment(const hvec2f& s0qp, const hvec2f& s0s1, const float& rSqr) {
    auto a = dot(s0s1, s0s1);
    //if( a != 0 ) // if you haven't zero-length segments omit this, as it would save you 1 _mm_comineq_ss() instruction and 1 memory fetch
    {
        auto b = dot(s0s1, s0qp);
        auto t = b / a; // length of projection of s0qp onto s0s1
        //std::cout << "t = " << t << "\n";
        if ((t >= 0) && (t <= 1)) // 
        {
            auto c = dot(s0qp, s0qp);
            auto r2 = c - a * t * t;
            return (r2 <= rSqr); // true if collides
        }
    }   
    return false;
}

Сумніваюсь, ви можете оптимізувати це далі. Я використовую його для виявлення зіткнень автомобільних гонок, керованих нейронною мережею, для обробки мільйонів мільйонів кроків ітерації.


Якщо відрізок лінії перетинає коло, але лише трохи, щоб він не пройшов його центральну точку, чи не повернеться ця функція помилковою, коли вона повинна повернути істину? Значення t може бути поза діапазоном 0..1.
Кріс

1

Ця функція Java повертає об’єкт DVec2. Для центру кола, радіуса кола та лінії потрібен DVec2 .

public static DVec2 CircLine(DVec2 C, double r, Line line)
{
    DVec2 A = line.p1;
    DVec2 B = line.p2;
    DVec2 P;
    DVec2 AC = new DVec2( C );
    AC.sub(A);
    DVec2 AB = new DVec2( B );
    AB.sub(A);
    double ab2 = AB.dot(AB);
    double acab = AC.dot(AB);
    double t = acab / ab2;

    if (t < 0.0) 
        t = 0.0;
    else if (t > 1.0) 
        t = 1.0;

    //P = A + t * AB;
    P = new DVec2( AB );
    P.mul( t );
    P.add( A );

    DVec2 H = new DVec2( P );
    H.sub( C );
    double h2 = H.dot(H);
    double r2 = r * r;

    if(h2 > r2) 
        return null;
    else
        return P;
}

1

Ось моє рішення в TypeScript, дотримуючись ідеї, яку запропонував @Mizipzor (використовуючи проекцію):

/**
 * Determines whether a line segment defined by a start and end point intersects with a sphere defined by a center point and a radius
 * @param a the start point of the line segment
 * @param b the end point of the line segment
 * @param c the center point of the sphere
 * @param r the radius of the sphere
 */
export function lineSphereIntersects(
  a: IPoint,
  b: IPoint,
  c: IPoint,
  r: number
): boolean {
  // find the three sides of the triangle formed by the three points
  const ab: number = distance(a, b);
  const ac: number = distance(a, c);
  const bc: number = distance(b, c);

  // check to see if either ends of the line segment are inside of the sphere
  if (ac < r || bc < r) {
    return true;
  }

  // find the angle between the line segment and the center of the sphere
  const numerator: number = Math.pow(ac, 2) + Math.pow(ab, 2) - Math.pow(bc, 2);
  const denominator: number = 2 * ac * ab;
  const cab: number = Math.acos(numerator / denominator);

  // find the distance from the center of the sphere and the line segment
  const cd: number = Math.sin(cab) * ac;

  // if the radius is at least as long as the distance between the center and the line
  if (r >= cd) {
    // find the distance between the line start and the point on the line closest to
    // the center of the sphere
    const ad: number = Math.cos(cab) * ac;
    // intersection occurs when the point on the line closest to the sphere center is
    // no further away than the end of the line
    return ad <= ab;
  }
  return false;
}

export function distance(a: IPoint, b: IPoint): number {
  return Math.sqrt(
    Math.pow(b.z - a.z, 2) + Math.pow(b.y - a.y, 2) + Math.pow(b.x - a.x, 2)
  );
}

export interface IPoint {
  x: number;
  y: number;
  z: number;
}

1

Я знаю, що минув час, коли ця нитка була відкритою. З відповіді, наданої chmike та вдосконаленою Акібом Мумтазом. Вони дають хорошу відповідь, але працюють лише для нескінченної лінії, як сказав Акіб. Тож я додаю кілька порівнянь, щоб знати, чи торкається відрізка лінії коло, я записую його в Python.

def LineIntersectCircle(c, r, p1, p2):
    #p1 is the first line point
    #p2 is the second line point
    #c is the circle's center
    #r is the circle's radius

    p3 = [p1[0]-c[0], p1[1]-c[1]]
    p4 = [p2[0]-c[0], p2[1]-c[1]]

    m = (p4[1] - p3[1]) / (p4[0] - p3[0])
    b = p3[1] - m * p3[0]

    underRadical = math.pow(r,2)*math.pow(m,2) + math.pow(r,2) - math.pow(b,2)

    if (underRadical < 0):
        print("NOT")
    else:
        t1 = (-2*m*b+2*math.sqrt(underRadical)) / (2 * math.pow(m,2) + 2)
        t2 = (-2*m*b-2*math.sqrt(underRadical)) / (2 * math.pow(m,2) + 2)
        i1 = [t1+c[0], m * t1 + b + c[1]]
        i2 = [t2+c[0], m * t2 + b + c[1]]

        if p1[0] > p2[0]:                                           #Si el punto 1 es mayor al 2 en X
            if (i1[0] < p1[0]) and (i1[0] > p2[0]):                 #Si el punto iX esta entre 2 y 1 en X
                if p1[1] > p2[1]:                                   #Si el punto 1 es mayor al 2 en Y
                    if (i1[1] < p1[1]) and (i1[1] > p2[1]):         #Si el punto iy esta entre 2 y 1
                        print("Intersection")
                if p1[1] < p2[1]:                                   #Si el punto 2 es mayo al 2 en Y
                    if (i1[1] > p1[1]) and (i1[1] < p2[1]):         #Si el punto iy esta entre 1 y 2
                        print("Intersection")

        if p1[0] < p2[0]:                                           #Si el punto 2 es mayor al 1 en X
            if (i1[0] > p1[0]) and (i1[0] < p2[0]):                 #Si el punto iX esta entre 1 y 2 en X
                if p1[1] > p2[1]:                                   #Si el punto 1 es mayor al 2 en Y
                    if (i1[1] < p1[1]) and (i1[1] > p2[1]):         #Si el punto iy esta entre 2 y 1
                        print("Intersection")
                if p1[1] < p2[1]:                                   #Si el punto 2 es mayo al 2 en Y
                    if (i1[1] > p1[1]) and (i1[1] < p2[1]):         #Si el punto iy esta entre 1 y 2
                        print("Intersection")

        if p1[0] > p2[0]:                                           #Si el punto 1 es mayor al 2 en X
            if (i2[0] < p1[0]) and (i2[0] > p2[0]):                 #Si el punto iX esta entre 2 y 1 en X
                if p1[1] > p2[1]:                                   #Si el punto 1 es mayor al 2 en Y
                    if (i2[1] < p1[1]) and (i2[1] > p2[1]):         #Si el punto iy esta entre 2 y 1
                        print("Intersection")
                if p1[1] < p2[1]:                                   #Si el punto 2 es mayo al 2 en Y
                    if (i2[1] > p1[1]) and (i2[1] < p2[1]):         #Si el punto iy esta entre 1 y 2
                        print("Intersection")

        if p1[0] < p2[0]:                                           #Si el punto 2 es mayor al 1 en X
            if (i2[0] > p1[0]) and (i2[0] < p2[0]):                 #Si el punto iX esta entre 1 y 2 en X
                if p1[1] > p2[1]:                                   #Si el punto 1 es mayor al 2 en Y
                    if (i2[1] < p1[1]) and (i2[1] > p2[1]):         #Si el punto iy esta entre 2 y 1
                        print("Intersection")
                if p1[1] < p2[1]:                                   #Si el punto 2 es mayo al 2 en Y
                    if (i2[1] > p1[1]) and (i2[1] < p2[1]):         #Si el punto iy esta entre 1 y 2
                        print("Intersection")

0

Ось рішення, написане голанг. Метод подібний до деяких інших відповідей, розміщених тут, але не зовсім однаковий. Це легко здійснити і пройшов випробування. Ось такі кроки:

  1. Перекладіть координати так, щоб коло було біля початку.
  2. Виразіть відрізок лінії у вигляді параметризованих функцій t для координат x і y. Якщо t дорівнює 0, значення функції - одна кінцева точка відрізка, а якщо t дорівнює 1, значення функції - інша кінцева точка.
  3. Розв’яжіть, якщо можливо, квадратичне рівняння, що виникає внаслідок обмежуючих значень t, що дають x, y координати з відстанями від початку, рівним радіусу кола.
  4. Викиньте рішення, де t <0 або> 1 (<= 0 або> = 1 для відкритого сегмента). Ці точки не містяться в сегменті.
  5. Перевести до початкових координат.

Значення для A, B і C для квадратики виведені тут, де (n-et) і (m-dt) - рівняння для координат лінії x і y відповідно. r - радіус кола.

(n-et)(n-et) + (m-dt)(m-dt) = rr
nn - 2etn + etet + mm - 2mdt + dtdt = rr
(ee+dd)tt - 2(en + dm)t + nn + mm - rr = 0

Тому A = ee + dd, B = - 2 (en + dm), а C = nn + mm - rr.

Ось код гололангу для функції:

package geom

import (
    "math"
)

// SegmentCircleIntersection return points of intersection between a circle and
// a line segment. The Boolean intersects returns true if one or
// more solutions exist. If only one solution exists, 
// x1 == x2 and y1 == y2.
// s1x and s1y are coordinates for one end point of the segment, and
// s2x and s2y are coordinates for the other end of the segment.
// cx and cy are the coordinates of the center of the circle and
// r is the radius of the circle.
func SegmentCircleIntersection(s1x, s1y, s2x, s2y, cx, cy, r float64) (x1, y1, x2, y2 float64, intersects bool) {
    // (n-et) and (m-dt) are expressions for the x and y coordinates
    // of a parameterized line in coordinates whose origin is the
    // center of the circle.
    // When t = 0, (n-et) == s1x - cx and (m-dt) == s1y - cy
    // When t = 1, (n-et) == s2x - cx and (m-dt) == s2y - cy.
    n := s2x - cx
    m := s2y - cy

    e := s2x - s1x
    d := s2y - s1y

    // lineFunc checks if the  t parameter is in the segment and if so
    // calculates the line point in the unshifted coordinates (adds back
    // cx and cy.
    lineFunc := func(t float64) (x, y float64, inBounds bool) {
        inBounds = t >= 0 && t <= 1 // Check bounds on closed segment
        // To check bounds for an open segment use t > 0 && t < 1
        if inBounds { // Calc coords for point in segment
            x = n - e*t + cx
            y = m - d*t + cy
        }
        return
    }

    // Since we want the points on the line distance r from the origin,
    // (n-et)(n-et) + (m-dt)(m-dt) = rr.
    // Expanding and collecting terms yeilds the following quadratic equation:
    A, B, C := e*e+d*d, -2*(e*n+m*d), n*n+m*m-r*r

    D := B*B - 4*A*C // discriminant of quadratic
    if D < 0 {
        return // No solution
    }
    D = math.Sqrt(D)

    var p1In, p2In bool
    x1, y1, p1In = lineFunc((-B + D) / (2 * A)) // First root
    if D == 0.0 {
        intersects = p1In
        x2, y2 = x1, y1
        return // Only possible solution, quadratic has one root.
    }

    x2, y2, p2In = lineFunc((-B - D) / (2 * A)) // Second root

    intersects = p1In || p2In
    if p1In == false { // Only x2, y2 may be valid solutions
        x1, y1 = x2, y2
    } else if p2In == false { // Only x1, y1 are valid solutions
        x2, y2 = x1, y1
    }
    return
}

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

package geom_test

import (
    "testing"

    . "**put your package path here**"
)

func CheckEpsilon(t *testing.T, v, epsilon float64, message string) {
    if v > epsilon || v < -epsilon {
        t.Error(message, v, epsilon)
        t.FailNow()
    }
}

func TestSegmentCircleIntersection(t *testing.T) {
    epsilon := 1e-10      // Something smallish
    x1, y1 := 5.0, 2.0    // segment end point 1
    x2, y2 := 50.0, 30.0  // segment end point 2
    cx, cy := 100.0, 90.0 // center of circle
    r := 80.0

    segx, segy := x2-x1, y2-y1

    testCntr, solutionCntr := 0, 0

    for i := -100; i < 100; i++ {
        for j := -100; j < 100; j++ {
            testCntr++
            s1x, s2x := x1+float64(i), x2+float64(i)
            s1y, s2y := y1+float64(j), y2+float64(j)

            sc1x, sc1y := s1x-cx, s1y-cy
            seg1Inside := sc1x*sc1x+sc1y*sc1y < r*r
            sc2x, sc2y := s2x-cx, s2y-cy
            seg2Inside := sc2x*sc2x+sc2y*sc2y < r*r

            p1x, p1y, p2x, p2y, intersects := SegmentCircleIntersection(s1x, s1y, s2x, s2y, cx, cy, r)

            if intersects {
                solutionCntr++
                //Check if points are on circle
                c1x, c1y := p1x-cx, p1y-cy
                deltaLen1 := (c1x*c1x + c1y*c1y) - r*r
                CheckEpsilon(t, deltaLen1, epsilon, "p1 not on circle")

                c2x, c2y := p2x-cx, p2y-cy
                deltaLen2 := (c2x*c2x + c2y*c2y) - r*r
                CheckEpsilon(t, deltaLen2, epsilon, "p2 not on circle")

                // Check if points are on the line through the line segment
                // "cross product" of vector from a segment point to the point
                // and the vector for the segment should be near zero
                vp1x, vp1y := p1x-s1x, p1y-s1y
                crossProd1 := vp1x*segy - vp1y*segx
                CheckEpsilon(t, crossProd1, epsilon, "p1 not on line ")

                vp2x, vp2y := p2x-s1x, p2y-s1y
                crossProd2 := vp2x*segy - vp2y*segx
                CheckEpsilon(t, crossProd2, epsilon, "p2 not on line ")

                // Check if point is between points s1 and s2 on line
                // This means the sign of the dot prod of the segment vector
                // and point to segment end point vectors are opposite for
                // either end.
                wp1x, wp1y := p1x-s2x, p1y-s2y
                dp1v := vp1x*segx + vp1y*segy
                dp1w := wp1x*segx + wp1y*segy
                if (dp1v < 0 && dp1w < 0) || (dp1v > 0 && dp1w > 0) {
                    t.Error("point not contained in segment ", dp1v, dp1w)
                    t.FailNow()
                }

                wp2x, wp2y := p2x-s2x, p2y-s2y
                dp2v := vp2x*segx + vp2y*segy
                dp2w := wp2x*segx + wp2y*segy
                if (dp2v < 0 && dp2w < 0) || (dp2v > 0 && dp2w > 0) {
                    t.Error("point not contained in segment ", dp2v, dp2w)
                    t.FailNow()
                }

                if s1x == s2x && s2y == s1y { //Only one solution
                    // Test that one end of the segment is withing the radius of the circle
                    // and one is not
                    if seg1Inside && seg2Inside {
                        t.Error("Only one solution but both line segment ends inside")
                        t.FailNow()
                    }
                    if !seg1Inside && !seg2Inside {
                        t.Error("Only one solution but both line segment ends outside")
                        t.FailNow()
                    }

                }
            } else { // No intersection, check if both points outside or inside
                if (seg1Inside && !seg2Inside) || (!seg1Inside && seg2Inside) {
                    t.Error("No solution but only one point in radius of circle")
                    t.FailNow()
                }
            }
        }
    }
    t.Log("Tested ", testCntr, " examples and found ", solutionCntr, " solutions.")
}

Ось результат тесту:

=== RUN   TestSegmentCircleIntersection
--- PASS: TestSegmentCircleIntersection (0.00s)
    geom_test.go:105: Tested  40000  examples and found  7343  solutions.

Нарешті, метод легко поширюється на випадок променя, що починається в одній точці, проходить через іншу і поширюється до нескінченності, лише перевіряючи, якщо t> 0 або t <1, але не обидва.


0

Мені просто це було потрібно, тому я придумав це рішення. Мова є максимальною скриптом, але її слід легко перекласти на будь-яку іншу мову. sideA, sideB і CircleRadius - скаляри, решта змінних - точки [x, y, z]. Я припускаю, що z = 0 розв’язати на площині XY

fn projectPoint p1 p2 p3 = --project  p1 perpendicular to the line p2-p3
(
    local v= normalize (p3-p2)
    local p= (p1-p2)
    p2+((dot v p)*v)
)
fn findIntersectionLineCircle CircleCenter CircleRadius LineP1 LineP2=
(
    pp=projectPoint CircleCenter LineP1 LineP2
    sideA=distance pp CircleCenter
    --use pythagoras to solve the third side
    sideB=sqrt(CircleRadius^2-sideA^2) -- this will return NaN if they don't intersect
    IntersectV=normalize (pp-CircleCenter)
    perpV=[IntersectV.y,-IntersectV.x,IntersectV.z]
    --project the point to both sides to find the solutions
    solution1=pp+(sideB*perpV)
    solution2=pp-(sideB*perpV)
    return #(solution1,solution2)
)

0

Рішення в python, засноване на @Joe Skeen

def check_line_segment_circle_intersection(line, point, radious):
    """ Checks whether a point intersects with a line defined by two points.

    A `point` is list with two values: [2, 3]

    A `line` is list with two points: [point1, point2]

    """
    line_distance = distance(line[0], line[1])
    distance_start_to_point = distance(line[0], point)
    distance_end_to_point = distance(line[1], point)

    if (distance_start_to_point <= radious or distance_end_to_point <= radious):
        return True

    # angle between line and point with law of cosines
    numerator = (math.pow(distance_start_to_point, 2)
                 + math.pow(line_distance, 2)
                 - math.pow(distance_end_to_point, 2))
    denominator = 2 * distance_start_to_point * line_distance
    ratio = numerator / denominator
    ratio = ratio if ratio <= 1 else 1  # To account for float errors
    ratio = ratio if ratio >= -1 else -1  # To account for float errors
    angle = math.acos(ratio)

    # distance from the point to the line with sin projection
    distance_line_to_point = math.sin(angle) * distance_start_to_point

    if distance_line_to_point <= radious:
        point_projection_in_line = math.cos(angle) * distance_start_to_point
        # Intersection occurs whent the point projection in the line is less
        # than the line distance and positive
        return point_projection_in_line <= line_distance and point_projection_in_line >= 0
    return False

def distance(point1, point2):
    return math.sqrt(
        math.pow(point1[1] - point2[1], 2) +
        math.pow(point1[0] - point2[0], 2)
    )

0
Function lineCircleCollision(p1,p2,c,r,precision){
Let dx = (p2.x-p1.x)/precision
Let dy = (p2.y-p1.y)/precision
Let collision=false
For(let i = 0;i<precision:i++){
If(Math.sqrt((p1.x+dx*i-c.x)**2+(p1.y+dy*i-c.y)**2).<r {
Collision=true
}
}

Ви можете взяти X рівномірно розташованих точок від прямої, і якщо такі знаходяться всередині кола, відбувається зіткнення

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