判断多边形是否相交_线性对象与三角形的相交测试

c5f74be5d8e13f5474b67af1d359c69f.png
chopper:目录​zhuanlan.zhihu.com
ea0770e4a986d1245c13df72f0e8be4d.png

本篇文章介绍一种三维空间下线性对象与三角形的相交测试算法和实现,线性对象包括:直接、射线和线段,它们可以用类似的形式来表示,先以直接为例来阐述算法,射线和线段是直接的特殊情况。

文章目录:

  • 算法介绍
  • 源码实现
  • 参考

算法介绍

在三维空间上,判断线性对象

与三角形
是否相交,若相交,求出交点,三角形所在的平面是

0fbad02cdef2192245db69bf0ec34638.png
图1. 直线与三角形的四种关系

三维空间的模型通常是由一个个三角形面片构成的,线性对象与三角形的相交检测也显得特别的重要。如图1所示,一条直线与三角形可能有四种关系:1)直线

与三角形相交;2)直线
与三角形所在的平面相交,但与三角形不相交;3)直线
与在三角形所在的平面上;4)直线
与三角形所在的平面平行。 一种朴素的检测算法是计算线性对象与三角形所在的平面的交点,再判断交点是否在三角形内。如果交点在三角形内,则线性对象与三角形相交;否则,不相交。这种算法效率不仅效率较低,而且更容易造成浮点数误差。因为计算机中的浮点数占用字节有限,能表示的精度有限,有效数较少,求直线与平面的交点,由于对同一个变量多次的乘法或者除法操作,造成误差的叠加,最后计算出来的结果由于精度问题,可能造成较大的误差。例如,三角形顶点的坐标数值较大,计算的直线与平面的交点,会由于精度误差导致求出的交点偏离平面,即可以检测到交点不在平面上。

Möller&Trumbore(1997)[1]提出了一种更加高效的射线与三角形相交测试的算法,而且乘法或者除法操作造成的误差叠加较少,精度损失更小,因此性能上更加健壮。

三角形内的任意一点都可以用它相对于三角形的顶点的位置来定义:

其中,

,称为重心坐标(barycentric coordinate)。直线可以用参数方程表示为
,因此计算直线与三角的交点的等式为

整理,得

这相当于是一个齐次线性方程组,

是它的解,可以采用克拉姆法则来求解,得到

其中,

,依据线性代数的知识,易知
,因此等式(4)可以写成:

如果得到的解

,那么直线与平面的交点位于三角形内;否则,位于三角形外。注意,若
,则交点在三角形的边上。

直线与三角形相交测试的算法伪码如下所示:

在三维空间上,判断直线
与三角形
是否相交,若相交,求出交点,三角形所在的平面是

14fdbe9b91b5f6b03d8183ee7ecacfce.png

如果需要求射线与三角形的交,必需限定

的取值范围,
必需满足条件
,若计算出来的
,则射线与三角形不相交。同样,如果需要求线段与三角形的交,线段的参数方程仍然可以表示为
,线段的两个端点分别是
,则
必需满足条件
,因此,若计算出来的
,则线段与三角形不相交。

注意到,在上述的算法伪码中,已经把一些中间变量存储起来,避免重复计算,可以提高算法的速度,例如中间变量

源码实现

基础库源码链接,参见这里,下面是前面所描述的算法的实现。

#include "SrGeometricTools.h"
#include "SrDataType.h"

namespace {
    class SrSegment3D
    {
    public:
        SrPoint3D mPoint1;
        SrPoint3D mPoint2;
    };

    class SrRay3D
    {
    public:
        SrPoint3D   mBase;
        SrPoint3D   mDirection;
    };

    class SrLine3D
    {
    public:
        SrPoint3D mBase;
        SrPoint3D mDirection;
    };

    /**
    brief 3D triangle class.
    This is a 3D triangle class with public data members.
    */

    class SrTriangle3D
    {
    public:
        /**
        brief Default constructor, endpoints is set to (0,0).
        */
        SrTriangle3D()
        {
            mPoint[0] = mPoint[1] = mPoint[1] = SrVector3D(0, 0, 0);
        }
        /**
        brief The line is initialized by three points.
        */
        SrTriangle3D(const SrPoint3D& p0, const SrPoint3D& p1, const SrPoint3D& p2)
        {
            mPoint[0] = p0;
            mPoint[1] = p1;
            mPoint[2] = p2;
        }
        /**
        brief Destructor. Do nothing.
        */
        ~SrTriangle3D() {}
        /**
        brief The triangle is valid if the three points are not on a line.
        */
        bool    isValid() const
        {
            SrVector3D norm = normal();
            if (EQUAL(norm.x, 0) && EQUAL(norm.y, 0) && EQUAL(norm.z, 0))
                return false;
            return true;
        }
        /**
        brief  Compute the normal using Newell method devised by Martin Newell.It relates to the order of the points.
        */
        const SrVector3D normal()const
        {
            SrVector3D norm = (mPoint[1] - mPoint[0]).cross(mPoint[2] - mPoint[0]);
            return norm;
        }
        /**
        brief  Judge whether or not the line hits the triangle.
        return SR_OVERLAPPING      if the line is on the plane where the triangle lies;
                SR_PARALLEL         if the line is parallel to the triangle.
                SR_DISJOINT         if the line misses the triangle;
                SR_INTERSECTING     if the line intersects with the triangle and intersection point is not on the edge.
        param[out] result If the line intersects with the triangle and the intersection point information is put in 'result'.
        */
        int lineHitTest(const SrLine3D& line, SrPoint3D& /*[OUT]*/ result)const
        {
            SrVector3D ret;
            int retFlag = linearIntersectTriangle(line.mBase, line.mDirection, ret);
            if (retFlag != SR_INTERSECTING)
                return retFlag;
            result = line.mBase + ret.z*line.mDirection;
            //check whether the intersection point is on the edge.
            //if( EQUAL(ret.x+ret.y-1.0,0) || 
            //  EQUAL(ret.x,0) || 
            //  EQUAL(ret.y,0) )
            //  return SR_INTERSECTING_EDGE;
            return SR_INTERSECTING;
        }

        /**
        brief  Judge whether or not the ray hits the triangle.
        return SR_OVERLAPPING      if the ray is on the plane where the triangle lies;
                SR_PARALLEL         if the ray is parallel to the triangle.
                SR_DISJOINT         if the ray misses the triangle;
                SR_INTERSECTING     if the ray intersects with the triangle and intersection point is not on the edge.
        param[out] result If the ray intersects with the triangle and the intersection point information is put in 'result'.
        */
        int rayHitTest(const SrRay3D& ray, SrPoint3D& /*[OUT]*/ result)const
        {
            SrVector3D ret;
            int retFlag = linearIntersectTriangle(ray.mBase, ray.mDirection, ret);
            if (retFlag != SR_INTERSECTING)
                return retFlag;
            //check t. t>=0
            if (LESS(ret.z, 0))
                return SR_DISJOINT;
            result = ray.mBase + ret.z*ray.mDirection;
            //check whether the intersection point is on the edge.
            //if( EQUAL(ret.x+ret.y-1.0,0) || 
            //  EQUAL(ret.x,0) || 
            //  EQUAL(ret.y,0) )
            //  return SR_INTERSECTING_EDGE;

            return SR_INTERSECTING;
        }
        /**
        brief  Judge whether or not the segment hits the triangle.
        return SR_OVERLAPPING       if the segment is on the plane where the triangle lies;
                SR_PARALLEL          if the segment is parallel to the triangle.
                SR_DISJOINT          if the segment misses the triangle;
                SR_INTERSECTING      if the segment intersects with the triangle and intersection point is not on the edge.
        param[out] result If the segment intersects with the triangle and the intersection point information is put in 'result'.
        */
        int segmentHitTest(const SrSegment3D& segment, SrPoint3D& /*[OUT]*/ result)const
        {
            SrVector3D ret;
            SrVector3D direction = segment.mPoint2 - segment.mPoint1;
            int retFlag = linearIntersectTriangle(segment.mPoint1, direction, ret);
            if (retFlag != SR_INTERSECTING)
                return retFlag;
            //check t. 0<= t <=1
            if (LESS(ret.z, 0) || GREATER(ret.z, 1))
                return SR_DISJOINT;
            result = segment.mPoint1 + ret.z*direction;
            //check whether the intersection point is on the edge.
            //if( EQUAL(ret.x+ret.y-1.0,0) || 
            //  EQUAL(ret.x,0) || 
            //  EQUAL(ret.y,0) )
            //  return SR_INTERSECTING_EDGE;
            return SR_INTERSECTING;
        }

    public:
        SrPoint3D   mPoint[3];

    private:
        /**
        brief  Judge whether or not the line hits the triangle.The line is base + t*direction.
                It is not necessary for direction to be unit length.
        return SR_OVERLAPPING       if the line is on the plane where the triangle lies;
                SR_PARALLEL          if the line is parallel to the triangle.
                SR_DISJOINT          if the line misses the triangle;
                SR_INTERSECTING      if the line intersects with the triangle.
        param[out] result return the result of (u,v,t) if the line intersects with the triangle.
        */
        /*
        --------------------------------------------------------------------------
        Devised by Moller and Trumbore,1997. <Fast,minimum storage ray-triangle
        intersection>
        Any point in a triangle can be defined in terms of its position relative
        to the triangle’s vertices:
                Qu,v = (1-u-v)V0 + uV1 + vV2 ,0<=u<=1,0<=v<=1,0<=u+v<=1
        For the linear component–triangle intersection,
                P + t*^d = Qu,v
        which can be expanded and applied Cramer’s rule to:
                |t|                         | |P-V0  V1-V0  V2-V0| |
                |u| = 1/|-^d  V1-V0  V2-V0| | |-^d   P-V0   V2-V0| |
                |v|                         | |-^d   V1-V0   P-V0| |


                                                | ((P-V0)×(V1-V0))·(V2-V0) |
                    = 1/(^d×(V2-v0))·(V1-V0)    |    (^d×(V2-V0))·(P-V0)   |
                                                |    ((P-V0)×(V1-V0))·^d     |
        --------------------------------------------------------------------------
        */
        int linearIntersectTriangle(const SrPoint3D& base, const SrVector3D& direction, SrVector3D& result)const
        {
            SrReal u, v, tmp;
            SrVector3D e1, e2, p, s, q;
            e1 = mPoint[1] - mPoint[0];
            e2 = mPoint[2] - mPoint[0];
            p = direction.cross(e2);
            tmp = p.dot(e1);
            //If the line is perpendicular to the normal of triangle.
            if (EQUAL(tmp, 0))
            {
                //The line is on the plane.
                p = e1.cross(e2);
                if (EQUAL(p.dot(base - mPoint[0]), 0))
                    return SR_OVERLAPPING;
                //The line is parallel to the plane.
                return SR_PARALLEL;
            }
            s = base - mPoint[0];
            u = p.dot(s) / tmp;
            if (LESS(u, 0) || GREATER(u, 1))
                return SR_DISJOINT;
            q = s.cross(e1);
            v = q.dot(direction) / tmp;
            if (LESS(v, 0) || GREATER(v, 1) || GREATER(u + v, 1))
                return SR_DISJOINT;

            result.x = u;
            result.y = v;
            result.z = e2.dot(q) / tmp;
            return SR_INTERSECTING;
        }
    };
}

参考

[1] Tomas Möller, and Ben Trumbore. "Fast, minimum storage ray-triangle intersection." Journal of graphics tools, vol.2, no.1, pp.21-28, 1997.

[2] Philip J. Schneider, and David H. Eberly. Geometric Tools for Computer Graphics. Morgan Kaufmann, 2002.

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值