Еще один алгоритм определения пересечения двух отрезков

Недавно была публикация "Простой алгоритм определения пересечения двух отрезков". Я решил попробовать решить задачу пересечения двух отрезков немного по-другому, более геометрически.
Нахождение точки пересечения двух отрезков.

Имеем 2 отрезка {P0,P1} и {P2,P3}, где P0,P1,P2,P3 точки на плоскости. Будем обозначать x y координаты точки P как P.x и P.y
Имеем координаты 4 точек в массиве P(0..3) структуры point(x float, y float):

1deacbf3ec5f4d20a36117a52fdaad1d.jpg

Шаг 1 — Перенос начала координат.

Запомним координаты точек P в дополнительном массиве P_copy. Перенесем начало системы координат в точку P0 и пересчитаем координаты точек:

P_copy = P
P(0).x = 0            ;  P(0).y = 0
for ii = 1 to 3 
   P(ii).x = P(ii).x - P_copy(1).x  ;  P(ii).y = P(ii).y - P_copy(1).y  
next 


Шаг 2 — Поворот начала координат
Повернем систему координат так, чтобы отрезок {P0,P1} принял вертикальное положение (лег на ось Y). Вычислим длину отрезка {P0,P1} как:
L1 = SQRT ( (P(1).x)^2 + (P(1).y)^2 )
Синус и косинус угла alfa поворота осей координат:

  если L1 > 0
     sin_alf = sin(alfa) = P(1).x / L1
     cos_alf = cos(alfa) = P(1).y / L1          
   если L1 = 0  // точку не поворачиваем
     sin_alf = 0
     cos_alf = 1


Cнова пересчитываем координаты точек P1,P2,P3:

   P(0).x = 0  ; P(0).y = 0  // Точка P0 не поворачивается, она в начале координат
   P(1).x = 0  ; P(1).y = L1
   P(2).x = P(2).x * cos_alf - P(2).y * sin_alf 
   P(2).y = P(2).y * cos_alf + P(2).x * sin_alf  
   P(3).x = P(3).x * cos_alf - P(3).y * sin_alf 
   P(3).y = P(3).y * cos_alf + P(3).x * sin_alf 


Шаг 3 — Поиск точки пересечения отрезков.

Запишем уравнение отрезка {P2,P3} и найдем точку его пересечения CR с осью Y:

P23X = P(2).x + ( P(3).x - P(2).x ) * beta
P23Y = P(2).y + ( P(3).y - P(2).y ) * beta
где 0 <= beta <= 1

В точке CR пересечения отрезка {P2,P3} с осью Y:
P(2).x + ( P(3).x - P(2).x ) * beta =0
Далее возможны 2 варианта в зависимости от значения P(3).x — P(2).x:

1 вариант:

  если  ( P(2).x - P(3).x ) <> 0 // отрезок {P2,P3} не вертикален
      beta  = P(2).x / ( P(2).x - P(3).x )
          если  beta >= 0 и beta <= 1  // отрезок {P2,P3} пересекает ось Y
            CR.x = 0 
        CR.y =  P(2).y + ( P(3).y - P(2).y ) *  beta
        //условие пересечения отрезков:         
               0 <= CR.y <= L1      


2 вариант:

Если P(2).x = P(3).x, то это означает, что отрезок {P2,P3} вертикален и параллелен отрезку {P0,P1}. Пересечение отрезков возможно только если второй отрезок {P2,P3} тоже лежит на оси Y, и один из его концов лежит в первом отрезке {P0,P1} (или касается) или отрезок {P2,P3} накрывает {P0,P1}. Будем считать что для результата нам достаточно одной точки. Это будет одна из точек P0..P3.

Условия:

   P(2).x = P(3).x = 0   // второй отрезок {P2,P3} вертикален b лежит на оси Y.
          и  // условие пересечения:
                P(2).y >= 0 и  P(2).y <= L1  // P2 внутри отрезка {P0,P1}
                         -> CR  = P_copy(2)   // результат выбираем вершину P2
                 или 
                P(3).y >= 0 и  P(3).y <= L1  // P3 внутри отрезка {P0,P1}
                    -> CR  = P_copy(3)  // результат выбираем вершину P3
                 или   
                     P(2).y < 0 и  P(3).y > L1  // отрезок {P0,P1} внутри отрезка {P2,P3}
                        или
                     P(3).y < 0 и  P(2).y > L1   
                  -> CR  = P_copy(0) // // результат выбираем вершину P0 (или P1)
            )    


Шаг 4

Если точка пересечения CR найдена по варианту 1 шага 3, то ее координаты пересчитываем для исходной системы координат. Используем переменные сохраненные на 1 и 2 шаге. Поворот на угол -alfa:

   CR.x = CR.x * cos(-alfa) + CR.y * sin(-alfa)  = CR.x * cos_alf + CR.y * sin_alf
   CR.y = CR.y * cos(-alfa) - CR.x * sin(-alfa)  = CR.y * cos_alf  - CR.x * sin_alf


Перенос обратно:

   CR.x = CR.x + P_copy(0).x 
   CR.y = CR.y + P_copy(0).y    


Если точка пересечения CR найдена по условию 2 шага 3 пересчет координат не нужен. Пример кода на golang под катом. Golang ом я только балуюсь, поэтому к коду прошу быть снисходительным. Код можно запустить на golang.org:

код на golang


// line_cross project main.go
package main
import (
«fmt»
«math»
)
type point struct {
x float64
y float64
}
func main() {
var P [4]point
var P_copy [4]point
var L1, sin_alf, cos_alf, wsp1, wsp2, beta float64
var flag_cross bool
var CR point
// исходные данные x y координаты точек
P[0].x = 1.0
P[0].y = 2.0
P[1].x = 10.0
P[1].y = 20.0
P[2].x = 3.0
P[2].y = 9.0
P[3].x = 9.0
P[3].y = 3.0
// шаг 1
P_copy = P
fmt.Println(«Исходные данные:»)
for ii := 0; ii < 4; ii++ {
fmt.Println(" p", ii+1, " x", P_copy[ii].x, " y", P_copy[ii].y)
}
P[0].x = 0
P[0].y = 0
for ii := 1; ii < 4; ii++ {
P[ii].x = P[ii].x — P_copy[0].x
P[ii].y = P[ii].y — P_copy[0].y
}
// шаг 2
L1 = math.Sqrt(P[1].x*P[1].x + P[1].y*P[1].y)
if L1 > 0 {
sin_alf = P[1].x / L1
cos_alf = P[1].y / L1
} else {
sin_alf = 0
cos_alf = 0
}
P[1].x = 0
P[1].y = L1
for ii := 2; ii < 4; ii++ {
wsp1 = P[ii].x*cos_alf — P[ii].y*sin_alf
wsp2 = P[ii].y*cos_alf + P[ii].x*sin_alf
P[ii].x = wsp1
P[ii].y = wsp2
}
//шаг 3
flag_cross = false
if P[2].x-P[3].x == 0 { // отрезок {P3,P4) параллелен {P0,P1)
if P[2].x == 0 && P[3].x == 0 {
switch {
case P[2].y >= 0 && P[2].y <= L1:
flag_cross = true
CR = P_copy[2]
case P[3].y >= 0 && P[3].y <= L1:
flag_cross = true
CR = P_copy[3]
case (P[2].y < 0 && P[3].y > L1) || (P[3].y < 0 && P[2].y > L1):
flag_cross = true
CR = P_copy[0]
}
}
} else {
beta = P[2].x / (P[2].x — P[3].x)
if beta >= 0 && beta <= 1 {
CR.x = 0
CR.y = P[2].y + (P[3].y-P[2].y)*beta
if CR.y >= 0 && CR.y <= L1 {
//шаг 4
// пересчет координат
flag_cross = true
wsp1 = CR.x*cos_alf + CR.y*sin_alf
wsp2 = CR.y*cos_alf — CR.x*sin_alf
CR.x = wsp1
CR.y = wsp2
CR.x = CR.x + P_copy[0].x
CR.y = CR.y + P_copy[0].y
}
}
}
if flag_cross {
fmt.Println(«Точка пересечения: х =», CR.x, " y=", CR.y)
} else {
fmt.Println(«Отрезки не пересекаются !»)
}
}

© Habrahabr.ru