Введение
Уже не раз на 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. Для этого решим систему уравнений:
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();
}
}
}
Надеюсь, вам понравилось. Удачи!