Точка пересечения двух отрезков

от автора

Введение

Уже не раз на habr затрагивалась данная тема: раз, два.

Я лишь хочу поделиться своей реализацией. Думаю, это кому-то пригодится.

Предупреждаю, данный метод не учитывает некоторые пограничные случаи и вхождение отрезков друг в друга.

Подготовка

Весь последующий код будет приведён на языке Java

Для начала создадим вспомогательный класс Dot. В нём всего две переменные x и y:

static class Dot {         float x,y;         Dot(float x, float y){             this.x =x;             this.y = y;         }      @Override         public String toString() {             return "x:"+x+" y:"+y;         }     }

И класс Vector2. В нём всего две функции: crs (cross) — векторное произведение, scl (scale) — масштабирование вектора.

public static class Vector2 {         float x,y;          public Vector2(Dot d1, Dot d2) {             this.x = d2.x-d1.x;             this.y = d2.y-d1.y;         }          /** Calculates the 2D cross product between this and the given vector.          * @param v2 the other vector          * @return the cross product (Z vector) */         public static float crs(Vector2 v1, Vector2 v2) {             return v1.x * v2.y - v1.y * v2.x;         }          /** Multiplies this vector by a scalar */         public Vector2 scl (float scalar) {             x *= scalar;             y *= scalar;             return this;         } }

Задача 1: определить, касаются ли отрезки.

Решение:
Из векторного произведения мы можем узнать, обхо́дим мы вектор по часовой или против часовой. Это поможет понять где находятся точки. Соединим точки A и D, A и C.

Перемножим вектора main и v2, main и v1. Если полученные произведения имеют разные знаки, значит точки C и D находятся по разные стороны относительно отрезка AB. Назовём такой метод fromDifferentSides:

public static boolean fromDifferentSides(Vector2 main, Vector2 v1, Vector2 v2){     float product1 =crs(main ,v1), product2 = crs(main ,v2);     return (product1>=0&&product2<=0 || product1<=0&&product2>=0); }

Таким же образом необходимо проверить точки A и B относительно отрезка CD. Соединим это в один метод и получится следующее:

public static boolean linesIntersect(Dot a, Dot b, Dot c, Dot d) {     Vector2 main = new Vector2(a,b);     Vector2 v1 = new Vector2(a,c);     Vector2 v2 = new Vector2(a,d);      if (fromDifferentSides(main,v1,v2)) {       main = new Vector2(c,d);       v1 =new Vector2(c,a);       v2 =new Vector2(c,b);       return fromDifferentSides(main,v1,v2);     }     return false; }

Задача 2: определить точку касания.

Решение:
Как и в предыдущей задаче определяем, касаются ли отрезки. Если касаются, начинаем определять эту точку. Через подобие найдём длину DO. Коэффициент подобия (k) равен отношению уже известных нам векторных произведений:

//коэффициент подобия double k = Math.abs(product2 / product1); if (Float.isInfinite((float) k)) return c;

В третей строчке проверяем пограничный случай. Теперь узнаем чему равно DO. Для этого решим систему уравнений:

\begin{cases}  & \text{ DO/CO = k }  \\   & \text{ DO+CO = CD}   \end{cases}

DO = CO*k
Подставляем во второе уравнение:
CO*k+CO = CD
CO(k+1)= CD
CO = CD/(k+1)
В итоге:
DO = (CD/(k+1))*k

Теперь создаём вектор CD и уменьшаем его до длинны DO. Но поскольку мы будем его умножать, нужно взять обратное от (k+1)*k:

//вектор DC Vector2 dc = new Vector2(d, c);  //уменьшаем вектор dc.scl((float) (1 / (k + 1)*k));

Теперь осталось добавить вектор к точке D:

//добавляем вектор к точке return new Dot(d.x + dc.x, d.y + dc.y);

Вот и всё! Мы получили заветную точку.

Предупреждаю, данный метод не учитывает некоторые пограничные случаи и вхождение отрезков друг в друга.

Функцию нахождения точки я назвал pointOfIntersection. Привожу полный код на Java:

 public class Main {     public static void main(String[] args) {         System.out.println(pointOfIntersection(new Dot(0, 0),new Dot(5, 0), new Dot(2, 3),new Dot(2, 0) ));     }      public static Dot pointOfIntersection(Dot a, Dot b, Dot c, Dot d) {          Vector2 main = new Vector2(c,d);         Vector2 v1= new Vector2(c,a);         Vector2 v2 = new Vector2(c,b);         if (fromDifferentSides(main,v1,v2)) {             main = new Vector2(a,b);             v1 = new Vector2(a,c);             v2 = new Vector2(a,d);              double product1 = crs(main ,v1), product2 = crs(main ,v2);             if (product1>=0&&product2<=0 || product1<=0&&product2>=0) {                 //коэффициент подобия                 double k = Math.abs(product2 / product1);                 if (Float.isInfinite((float) k)) return c;                  //вектор DC                 Vector2 dc = new Vector2(d, c);                  //уменьшаем вектор                 dc.scl((float) (1 / (k + 1)*k));                  //добавляем вектор к точке                 return new Dot(d.x + dc.x, d.y + dc.y);             }          }          return null;     }      public static boolean linesIntersect(Dot a, Dot b, Dot c, Dot d) {         Vector2 main = new Vector2(a,b);         Vector2 v1 = new Vector2(a,c);         Vector2 v2 = new Vector2(a,d);          if (fromDifferentSides(main,v1,v2)) {             main = new Vector2(c,d);             v1 =new Vector2(c,a);             v2 =new Vector2(c,b);             return fromDifferentSides(main,v1,v2);         }         return false;     }      static class Dot {         float x,y;         Dot(float x, float y){             this.x =x;             this.y = y;         }          @Override         public String toString() {             return "x:"+x+" y:"+y;         }     }       public static class Vector2 {         float x,y;          public Vector2(Dot d1, Dot d2) {             this.x = d2.x-d1.x;             this.y = d2.y-d1.y;         }          /** Calculates the 2D cross product between this and the given vector.          * @param v2 the other vector          * @return the cross product (Z vector) */         public static float crs(Vector2 v1, Vector2 v2) {             return v1.x * v2.y - v1.y * v2.x;         }          /** Multiplies this vector by a scalar */         public Vector2 scl (float scalar) {             x *= scalar;             y *= scalar;             return this;         }          public static boolean fromDifferentSides(Vector2 main, Vector2 v1, Vector2 v2){             float product1 =crs(main ,v1), product2 = crs(main ,v2);             return (product1>=0&&product2<=0 || product1<=0&&product2>=0);         }        //функция округления         public static double round(double value, int places) {             if (places < 0) throw new IllegalArgumentException();             BigDecimal bd = new BigDecimal(Double.toString(value));             bd = bd.setScale(places, RoundingMode.HALF_UP);             return bd.doubleValue();         }     } } 

Надеюсь, вам понравилось. Удачи!


ссылка на оригинал статьи https://habr.com/ru/articles/578746/