Какой самый эффективный способ обнаружить пересечение треугольника с треугольником?

Как узнать, пересекаются ли два треугольника в двухмерном евклидовом пространстве? (т.е. классическая 2D-геометрия) с учетом координат (X, Y) каждой вершины в каждом треугольнике.


person José Joel.    schedule 18.10.2009    source источник
comment
Что касается действительно самого эффективного алгоритма, то над этим вопросом не было проделано много работы - никто точно не показал, какой вариант является самым быстрым. Одна из проблем заключается в том, что многие дискуссии касаются трис в трехмерном пространстве. Например, realtimecollisiondetection.net/blog/?p=29 PS Такие проблемы часто возникают из-за того, что точки находятся на правильной стороне отрезка линии. Например, mochima.com/articles/cuj_geometry_article/ Как указывает Ник в своем последнем пара, на практике все зависит от того, насколько хорошо вы отбираете.   -  person Fattie    schedule 27.12.2011


Ответы (6)


Один из способов - проверить, пересекаются ли две стороны треугольника A с любой стороной треугольника B, а затем проверьте все шесть возможностей точки A внутри B или точки B внутри A.

Для точки внутри треугольника см., Например: Точка в тесте треугольника.

Когда мы тестируем столкновения на полигонах, у нас также есть окружающий прямоугольник для наших полигонов. Итак, сначала мы проверяем на предмет столкновений прямоугольников, и если есть попадание, мы переходим к обнаружению столкновений многоугольников.

person Nick Dandoulakis    schedule 18.10.2009
comment
привет @Joe. Правильно, что мы должны сравнить все 3 стороны A со всеми 3 сторонами B. Но поскольку мы собираемся проверить, находятся ли углы A внутри B (и наоборот) - после проверки пересечения отрезков линии - вся процедура все еще работает . Это потому, что если мы обнаружим любой угол внутри другого треугольника, мы получим столкновение. - person Nick Dandoulakis; 27.12.2011
comment
Требуется всего 4 балла в тестах на треугольник. jsfiddle.net/eyal/gxw3632c Это не быстрый алгоритм пересечения треугольника с треугольником. - person Eyal; 30.05.2017

Реализация Python для пересечения линий и точка в тесте треугольника с небольшими изменениями.

def line_intersect2(v1,v2,v3,v4):
    '''
    judge if line (v1,v2) intersects with line(v3,v4)
    '''
    d = (v4[1]-v3[1])*(v2[0]-v1[0])-(v4[0]-v3[0])*(v2[1]-v1[1])
    u = (v4[0]-v3[0])*(v1[1]-v3[1])-(v4[1]-v3[1])*(v1[0]-v3[0])
    v = (v2[0]-v1[0])*(v1[1]-v3[1])-(v2[1]-v1[1])*(v1[0]-v3[0])
    if d<0:
        u,v,d= -u,-v,-d
    return (0<=u<=d) and (0<=v<=d)

def point_in_triangle2(A,B,C,P):
    v0 = [C[0]-A[0], C[1]-A[1]]
    v1 = [B[0]-A[0], B[1]-A[1]]
    v2 = [P[0]-A[0], P[1]-A[1]]
    cross = lambda u,v: u[0]*v[1]-u[1]*v[0]
    u = cross(v2,v0)
    v = cross(v1,v2)
    d = cross(v1,v0)
    if d<0:
        u,v,d = -u,-v,-d
    return u>=0 and v>=0 and (u+v) <= d

def tri_intersect2(t1, t2):
    '''
    judge if two triangles in a plane intersect 
    '''
    if line_intersect2(t1[0],t1[1],t2[0],t2[1]): return True
    if line_intersect2(t1[0],t1[1],t2[0],t2[2]): return True
    if line_intersect2(t1[0],t1[1],t2[1],t2[2]): return True
    if line_intersect2(t1[0],t1[2],t2[0],t2[1]): return True
    if line_intersect2(t1[0],t1[2],t2[0],t2[2]): return True
    if line_intersect2(t1[0],t1[2],t2[1],t2[2]): return True
    if line_intersect2(t1[1],t1[2],t2[0],t2[1]): return True
    if line_intersect2(t1[1],t1[2],t2[0],t2[2]): return True
    if line_intersect2(t1[1],t1[2],t2[1],t2[2]): return True
    inTri = True 
    inTri = inTri and point_in_triangle2(t1[0],t1[1],t1[2], t2[0])
    inTri = inTri and point_in_triangle2(t1[0],t1[1],t1[2], t2[1])
    inTri = inTri and point_in_triangle2(t1[0],t1[1],t1[2], t2[2])
    if inTri == True: return True
    inTri = True
    inTri = inTri and point_in_triangle2(t2[0],t2[1],t2[2], t1[0])
    inTri = inTri and point_in_triangle2(t2[0],t2[1],t2[2], t1[1])
    inTri = inTri and point_in_triangle2(t2[0],t2[1],t2[2], t1[2])
    if inTri == True: return True
    return False
person Martin Wang    schedule 29.04.2014
comment
В этом случае получается неправильный ответ: t1 = [[0,0],[5,0],[0,5]]; t2 = [[-10,0],[-5,0],[-1,6]]; print (tri_intersect2(t1, t2), False) - person TimSC; 27.03.2016
comment
@TimSC Да, не удается обнаружить пересечение двух параллельных линий. Вы можете обеспечить соблюдение этого | d | больше, чем небольшое положительное число в функции line_intersect2. - person Martin Wang; 28.03.2016
comment
Вам не нужно делать все 9 пересечений линий, вы можете сделать только 8. Потому что, если один из треугольников пересекает другой, он также должен пересекаться обратно. Итак, если есть 1 перекресток, их должно быть два. Кроме того, вам не нужны все точки в тестах на треугольник, потому что, если нет пересечений, то либо все точки находятся внутри, либо нет ни одной. Таким образом, вы можете сделать 8 line_intersect и 2 точки в Triangle. Или сделайте 6 line_intersect, а затем 6 точек в треугольнике. Зависит от того, что быстрее для вас. - person Eyal; 07.05.2017

Вот моя попытка решить проблему столкновения треугольника с треугольником (реализована на Python):

#2D Triangle-Triangle collisions in python
#Release by Tim Sheerman-Chase 2016 under CC0

import numpy as np

def CheckTriWinding(tri, allowReversed):
    trisq = np.ones((3,3))
    trisq[:,0:2] = np.array(tri)
    detTri = np.linalg.det(trisq)
    if detTri < 0.0:
        if allowReversed:
            a = trisq[2,:].copy()
            trisq[2,:] = trisq[1,:]
            trisq[1,:] = a
        else: raise ValueError("triangle has wrong winding direction")
    return trisq

def TriTri2D(t1, t2, eps = 0.0, allowReversed = False, onBoundary = True):
    #Trangles must be expressed anti-clockwise
    t1s = CheckTriWinding(t1, allowReversed)
    t2s = CheckTriWinding(t2, allowReversed)

    if onBoundary:
        #Points on the boundary are considered as colliding
        chkEdge = lambda x: np.linalg.det(x) < eps
    else:
        #Points on the boundary are not considered as colliding
        chkEdge = lambda x: np.linalg.det(x) <= eps

    #For edge E of trangle 1,
    for i in range(3):
        edge = np.roll(t1s, i, axis=0)[:2,:]

        #Check all points of trangle 2 lay on the external side of the edge E. If
        #they do, the triangles do not collide.
        if (chkEdge(np.vstack((edge, t2s[0]))) and
            chkEdge(np.vstack((edge, t2s[1]))) and  
            chkEdge(np.vstack((edge, t2s[2])))):
            return False

    #For edge E of trangle 2,
    for i in range(3):
        edge = np.roll(t2s, i, axis=0)[:2,:]

        #Check all points of trangle 1 lay on the external side of the edge E. If
        #they do, the triangles do not collide.
        if (chkEdge(np.vstack((edge, t1s[0]))) and
            chkEdge(np.vstack((edge, t1s[1]))) and  
            chkEdge(np.vstack((edge, t1s[2])))):
            return False

    #The triangles collide
    return True

if __name__=="__main__":
    t1 = [[0,0],[5,0],[0,5]]
    t2 = [[0,0],[5,0],[0,6]]
    print (TriTri2D(t1, t2), True)

    t1 = [[0,0],[0,5],[5,0]]
    t2 = [[0,0],[0,6],[5,0]]
    print (TriTri2D(t1, t2, allowReversed = True), True)

    t1 = [[0,0],[5,0],[0,5]]
    t2 = [[-10,0],[-5,0],[-1,6]]
    print (TriTri2D(t1, t2), False)

    t1 = [[0,0],[5,0],[2.5,5]]
    t2 = [[0,4],[2.5,-1],[5,4]]
    print (TriTri2D(t1, t2), True)

    t1 = [[0,0],[1,1],[0,2]]
    t2 = [[2,1],[3,0],[3,2]]
    print (TriTri2D(t1, t2), False)

    t1 = [[0,0],[1,1],[0,2]]
    t2 = [[2,1],[3,-2],[3,4]]
    print (TriTri2D(t1, t2), False)

    #Barely touching
    t1 = [[0,0],[1,0],[0,1]]
    t2 = [[1,0],[2,0],[1,1]]
    print (TriTri2D(t1, t2, onBoundary = True), True)

    #Barely touching
    t1 = [[0,0],[1,0],[0,1]]
    t2 = [[1,0],[2,0],[1,1]]
    print (TriTri2D(t1, t2, onBoundary = False), False)

Он работает на основе того факта, что треугольники не перекрываются, если все точки треугольника 1 находятся на внешней стороне хотя бы одного из ребер треугольника 2 (или наоборот). Конечно, треугольники никогда не бывают вогнутыми.

Я не знаю, более или менее эффективен этот подход, чем другие.

Бонус: я перенес его на C ++ https://gist.github.com/TimSC/5ba18ae21c4459275f90

person TimSC    schedule 27.03.2016

Отсортируйте вершины обоих треугольников по убыванию ординаты. Для этого требуется не более трех сравнений на треугольник. Затем объедините две последовательности. Думаю, для этого потребуется не более пяти сравнений.

Теперь для каждой ординаты рассмотрим горизонтальную линию. Он пересекает оба треугольника не более чем в одном линейном сегменте, и легко проверить, действительно ли сегменты перекрываются. Если это так или если они меняют порядок между двумя линиями, то треугольники пересекаются.

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

person Yves Daoust    schedule 21.04.2021

Как уже говорилось, вам нужно убедиться, что точка находится внутри треугольника. Самый простой способ проверить, находится ли точка внутри замкнутого многоугольника, - провести прямую линию в любом направлении от точки и посчитать, сколько раз линия пересекает вершину. Если ответ нечетный, то точка находится в многоугольнике, четный - снаружи.

Простейшая прямая линия для проверки - это линия, проходящая горизонтально справа от точки (или в другом перпендикулярном направлении). Это делает проверку пересечения вершин почти тривиальной. Следующих проверок должно хватить:

  • Находится ли y-координата точки между y-координатами двух конечных точек вершины? Нет, тогда не переходит.

  • Координата x точки больше самой дальней правой конечной точки вершины? Да тогда не переступает.

  • Координата x точки меньше самой дальней левой конечной точки вершины? Да, значит, крест.

  • Если вышеперечисленные случаи терпят неудачу, вы можете использовать перекрестное произведение вектора, представляющего вершину, и вектора, образованного от конца вершины до точки. Отрицательный ответ будет означать, что точка лежит по одну сторону от вершины, положительный ответ - по другую сторону от вершины, а нулевой ответ - по вершине. Это работает, потому что кросс-произведение включает в себя синус двух векторов.

person Guillermo Phillips    schedule 26.10.2009
comment
Это не скажет вам, пересекаются ли два треугольника, о чем был вопрос. Вы не можете просто проверить вершины одного треугольника, поскольку треугольники могут пересекаться без того, чтобы вершины находились друг в друге (например, звезда Давида). - person Ergwun; 06.05.2015
comment
Вы действительно думаете, что это поможет. Какой самый эффективный способ обнаружить пересечение треугольника с треугольником? - person tvorez; 24.01.2019

На самом деле вам нужен алгоритм «Точка в многоугольнике». Если какая-либо из точек одного треугольника находится в другом, они пересекаются. Вот хороший вопрос, чтобы проверить.

Как определить, находится ли 2D-точка в пределах многоугольника?

person snicker    schedule 18.10.2009
comment
Это не даст общего решения, поскольку два треугольника могут перекрываться без того, чтобы одна из их вершин находилась внутри другого. - person gnovice; 18.10.2009
comment
если перекрываются только крошечные точки, трудно понять, какую точку выбрать для проверки - person Jerry Z.; 21.11.2017