检测两个三角形是否有重叠面积/相交的方法

为什么要写这篇文章呢? 在宾夕法尼亚大学的路径规划公开课第二周的课程中, 宾夕法尼亚大学Coursera运动规划公开课学习有感之二提到的那一周的作业用到的知识. 这个问题看似简单, 但是着实花了很长时间去想. 而且, 自己拍脑袋想的东西不一定完善, 方法总是有漏洞. 所以, 最后还是去Stack Overflow上寻找的答案, 选择了一个高票答案自己研究了一下分享在这里.
下面代码来自这些网址:
1. Geometry Concepts: Line Intersection and its Applications
2. How to determine if a point is in a 2D triangle?
3. Is this a better point in triangle test (2D)?

总思路: 判断两个三角形的边不互相切割; 并且两个三角形不互相包含. 总代码在最后.

首先, 需要判断是否存在两个三角形的边互相切割的情况. 总的思路来自上面网址1. 两个三角形一共有6条边, 两两切割就是9种情况. 对于每种情况, 需要先把此时考虑的两条线段变成两条直线求交点. 然后看求出来的交点是不是在线段的横纵坐标范围内. 如果在线段的横纵坐标范围内, 就说明两条线段是有交点的; 如果不在线段的横纵坐标范围内, 就说明两条线段没有交点. 只要出现两条线段有交点, 必然说明两个三角形相交, 判断算法就可以跳出了; 否则还要继续进行其他判断. 下面是代码中的详细解释, 强烈推荐把参考的网站上面讲的知识看一遍自己推导一遍就明白后面代码了, 否则减号乘号很多时候意义并不明朗.

for i = 1:3
    for j = 1:3 %下面用到取余是为了实现j从1变化到3的时候几条边的轮换
        A1 = P1(mod(j+1, 3)+1, 2) - P1(mod(j, 3)+1, 2);
        B1 = P1(mod(j, 3)+1, 1) - P1(mod(j+1, 3)+1, 1);
        C1 = A1*P1(mod(j, 3)+1, 1)+B1*P1(mod(j, 3)+1, 2);

        A2 = P2(mod(i+1, 3)+1, 2) - P2(mod(i, 3)+1, 2);
        B2 = P2(mod(i, 3)+1, 1) - P2(mod(i+1, 3)+1, 1);
        C2 = A2*P2(mod(i, 3)+1, 1)+B2*P2(mod(i, 3)+1, 2);

        det = A1*B2 - A2*B1;

        if det == 0 % 行列式为0说明两条直线平行, 就设置交点x和y是无穷
            x = inf;
            y = inf;
        else % 否则就能够求出交点坐标
            x = (B2*C1 - B1*C2)/det;
            y = (A1*C2 - A2*C1)/det;
        end

        if (min(P1(mod(j, 3)+1, 1), P1(mod(j+1, 3)+1, 1)) <= x) && (x <= max(P1(mod(j, 3)+1, 1), P1(mod(j+1, 3)+1, 1))) && (min(P1(mod(j+1, 3)+1, 2), P1(mod(j, 3)+1, 2)) <= y) && (y <= max(P1(mod(j+1, 3)+1, 2), P1(mod(j, 3)+1, 2))) 
            if (min(P2(mod(i, 3)+1, 1), P2(mod(i+1, 3)+1, 1)) <= x) && (x <= max(P2(mod(i, 3)+1, 1), P2(mod(i+1, 3)+1, 1))) && (min(P2(mod(i+1, 3)+1, 2), P2(mod(i, 3)+1, 2)) <= y) && (y <= max(P2(mod(i+1, 3)+1, 2), P2(mod(i, 3)+1, 2))) % % 如果交点横坐标在线段横坐标之间, 交点纵坐标在两条线段纵坐标之间, 那就说明两条线段有交点
            flag1 = true;
            break;
            end
        else
            flag1 = false;
        end
    end
    if flag1 == true;
        break;
    end
end

如果两个三角形的线段之间都没有交点, 也不能说明两个三角形就不相交, 还有可能是包含的关系.
判断两个三角形是不是互相包含的关系, 就需要判断一个三角形的顶点是不是在另一个三角形里. 例如 ,三角形A的所有顶点都不在三角形B内, 三角形B的所有顶点都不在三角形A内, 这才能证明两个三角形不是互相包含关系. 下面是代码的详细解释, 资料来源来自于上面网址2的最高票答案以及网址3.
下面这个sign函数的意思是判断p1点在p2和p3点构成的线段的一侧. 通过sign函数的正负可以区分在两侧. sign函数的实现原理应该是用到了矢量的叉乘. 假设p1(x,y), p2(m1, n1), p3(m2, n2), 则 BA=(m1m2,n1n2) , BP=(xm2,yn2) , 所以

BP×BA=ixm2m1m2jyn2n1n2k00=[(xm2)(n1n2)(m1m2)(yn2)]k

其值的正负表示p1点在被p2点和p3点分割的哪一个半平面. 这样就能够和下面的代码对上了.

function result = sign(p1, p2, p3)
    result =  (p1(1) - p3(1)) * (p2(2) - p3(2)) - (p2(1) - p3(1)) * (p1(2) - p3(2));
end

利用上面的sign函数, 如果三角形B的一个点在三角形A的每条边同一方向的半平面, 那一定说明这个点在三角形A内. 这就说明三角形B被三角形A整体包含在里面, 所以两个三角形互相包含, 也算是一种相交. 对于三角形B的每一个顶点都进行如此的判断(i=1:3), 只要判断相交就跳出. 就得到了下面的代码.

for i = 1:3
    b1 = sign(P2(i, :), P1(1, :), P1(2, :)) < 0.0;
    b2 = sign(P2(i, :), P1(2, :), P1(3, :)) < 0.0;
    b3 = sign(P2(i, :), P1(3, :), P1(1, :)) < 0.0;

    flag2 =  ((b1 == b2) && (b2 == b3));
    if flag2
        break;
    end
end

判断三角形A是不是被三角形B整体包含在里面.

for i = 1:3
    b1 = sign(P1(i, :), P2(1, :), P2(2, :)) < 0.0;
    b2 = sign(P1(i, :), P2(2, :), P2(3, :)) < 0.0;
    b3 = sign(P1(i, :), P2(3, :), P2(1, :)) < 0.0;

    flag3 =  ((b1 == b2) && (b2 == b3));
    if flag3
        break;
    end
end

只要有一种情况判断相交, flag就设置为真, 函数就返回三角形相交的结果.

if flag1 || flag2 || flag3
    flag = true;
else 
    flag = false;
end

end


全部的代码附在下面:

function flag = triangle_intersection(P1, P2)
% triangle_test : returns true if the triangles overlap and false otherwise

for i = 1:3
    for j = 1:3
        A1 = P1(mod(j+1, 3)+1, 2) - P1(mod(j, 3)+1, 2);
        B1 = P1(mod(j, 3)+1, 1) - P1(mod(j+1, 3)+1, 1);
        C1 = A1*P1(mod(j, 3)+1, 1)+B1*P1(mod(j, 3)+1, 2);

        A2 = P2(mod(i+1, 3)+1, 2) - P2(mod(i, 3)+1, 2);
        B2 = P2(mod(i, 3)+1, 1) - P2(mod(i+1, 3)+1, 1);
        C2 = A2*P2(mod(i, 3)+1, 1)+B2*P2(mod(i, 3)+1, 2);

        det = A1*B2 - A2*B1;

        if det == 0
            x = inf;
            y = inf;
        else
            x = (B2*C1 - B1*C2)/det;
            y = (A1*C2 - A2*C1)/det;
        end

        if (min(P1(mod(j, 3)+1, 1), P1(mod(j+1, 3)+1, 1)) <= x) && (x <= max(P1(mod(j, 3)+1, 1), P1(mod(j+1, 3)+1, 1))) && (min(P1(mod(j+1, 3)+1, 2), P1(mod(j, 3)+1, 2)) <= y) && (y <= max(P1(mod(j+1, 3)+1, 2), P1(mod(j, 3)+1, 2)))
            if (min(P2(mod(i, 3)+1, 1), P2(mod(i+1, 3)+1, 1)) <= x) && (x <= max(P2(mod(i, 3)+1, 1), P2(mod(i+1, 3)+1, 1))) && (min(P2(mod(i+1, 3)+1, 2), P2(mod(i, 3)+1, 2)) <= y) && (y <= max(P2(mod(i+1, 3)+1, 2), P2(mod(i, 3)+1, 2)))
            flag1 = true;
            break;
            end
        else
            flag1 = false;
        end
    end
    if flag1 == true;
        break;
    end
end

%% Judge whether a point lies inside of a triangle

for i = 1:3
    b1 = sign(P2(i, :), P1(1, :), P1(2, :)) < 0.0;
    b2 = sign(P2(i, :), P1(2, :), P1(3, :)) < 0.0;
    b3 = sign(P2(i, :), P1(3, :), P1(1, :)) < 0.0;

    flag2 =  ((b1 == b2) && (b2 == b3));
    if flag2
        break;
    end
end

for i = 1:3
    b1 = sign(P1(i, :), P2(1, :), P2(2, :)) < 0.0;
    b2 = sign(P1(i, :), P2(2, :), P2(3, :)) < 0.0;
    b3 = sign(P1(i, :), P2(3, :), P2(1, :)) < 0.0;

    flag3 =  ((b1 == b2) && (b2 == b3));
    if flag3
        break;
    end
end

if flag1 || flag2 || flag3
    flag = true;
else 
    flag = false;
end

end

function result = sign(p1, p2, p3)
    result =  (p1(1) - p3(1)) * (p2(2) - p3(2)) - (p2(1) - p3(1)) * (p1(2) - p3(2));
end

总结:
关于算法:
算法里面没有涉及除法, 没有涉及根号, 效率比较高. 但是上面的循环我觉得还是有点问题, 应该还是有可以简化的地方.
关于态度:
这个问题花了好久去想, 花了很长时间去测试代码验证是否有思维疏漏情况, 又花了一些时间记录这个过程. 我感觉还是很值得的. 看到这样一个看似简单的问题, 在Stack Overflow上如此热烈的讨论, 不禁让我很感动. 大家对于每个算法的效率, 细节, 思维逻辑都有着如此精益求精的态度, 这世界真是太美好了. 所以我更要认真的把想法记录下来.

你可能感兴趣的:(杂谈)