LineSegmentIntersectorUtils::IntersectFunctor::intersect源码分析

目录

1. 概述

2. 代码环境说明

3. intersect函数分析


1. 概述

      osgUtil::Intersector有几个子类,如下:

每个子类表示不同的求交器。所谓求交器就是判定和物体相交的类,通过这些类可以很方便的得出交点、实现拾取功能等。LineSegmentIntersector类是osgUtil::Intersector其中的一个子类,其表示线段求交器,即通过线段和三维场景中的某个物体相交,该类一般和osgUtil::IntersectionVisitor即求交访问器类一起使用,从而得出交点、实现拾取功能等。关于osgUtil::LineSegmentIntersector类的具体应用及源码分析,请参考如下博文:

2. 代码环境说明

环境说明如下:

  • OpenSceneGraph-3.6.2。
  • Windows 10。
  • Microsoft Visual Studio Community 2022 (64 位) - Current版本 17.5.5。

说明:本博文是基于OpenSceneGraph的3.6.2版本来讲解的,读者版本可能和本人的版本不同,

           故本人的源码或功能可能在细节上和读者的有所不同。

3. intersect函数分析

本节讲解的LineSegmentIntersectorUtils::IntersectFunctor::intersect函数源码如下: 

void intersect(const osg::Vec3& v0, const osg::Vec3& v1, const osg::Vec3& v2)
    {
        if (_settings->_limitOneIntersection && _hit) return;

        // const StartEnd startend = _startEndStack.back();
        // const osg::Vec3& ls = startend.first;
        // const osg::Vec3& le = startend.second;

        Vec3 T = _start - v0;
        Vec3 E2 = v2 - v0;
        Vec3 E1 = v1 - v0;

        Vec3 P =  _d ^ E2;

        value_type det = P * E1;

        value_type r,r0,r1,r2;

        const value_type epsilon = 1e-10;
        if (det>epsilon)
        {
            value_type u = (P*T);
            if (u<0.0 || u>det)
            {
                return;
            }

            osg::Vec3 Q = T ^ E1;
            value_type v = (Q*_d);
            if (v<0.0 || v>det)
            {
                return;
            }

            if ((u + v) > det)
            {
                return;
            }

            value_type inv_det = 1.0/det;
            value_type t = (Q*E2)*inv_det;
            if (t<0.0 || t>_length) return;

            u *= inv_det;
            v *= inv_det;

            r0 = 1.0-u-v;
            r1 = u;
            r2 = v;
            r = t * _inverse_length;
        }
        else if (det<-epsilon)
        {
            value_type u = (P*T);
            if (u>0.0 || u<det) return;

            Vec3 Q = T ^ E1;
            value_type v = (Q*_d);
            if (v>0.0 || v<det) return;

            if ((u+v) < det) return;

            value_type inv_det = 1.0/det;
            value_type t = (Q*E2)*inv_det;
            if (t<0.0 || t>_length) return;

            u *= inv_det;
            v *= inv_det;

            r0 = 1.0-u-v;
            r1 = u;
            r2 = v;
            r = t * _inverse_length;
        }
        else
        {
            return;
        }

        // Remap ratio into the range of LineSegment
        const osg::Vec3d& lsStart = _settings->_lineSegIntersector->getStart();
        const osg::Vec3d& lsEnd = _settings->_lineSegIntersector->getEnd();
        double remap_ratio =  ((_start - lsStart).length() + r*_length)/(lsEnd - lsStart).length();

        Vec3 in = lsStart*(1.0 - remap_ratio) + lsEnd*remap_ratio; // == v0*r0 + v1*r1 + v2*r2;
        Vec3 normal = E1^E2;
        normal.normalize();

        LineSegmentIntersector::Intersection hit;
        hit.ratio = remap_ratio;
        hit.matrix = _settings->_iv->getModelMatrix();
        hit.nodePath = _settings->_iv->getNodePath();
        hit.drawable = _settings->_drawable;
        hit.primitiveIndex = _primitiveIndex;

        hit.localIntersectionPoint = in;
        hit.localIntersectionNormal = normal;

        if (_settings->_vertices.valid())
        {
            const osg::Vec3* first = &(_settings->_vertices->front());
            hit.indexList.reserve(3);
            hit.ratioList.reserve(3);

            if (r0!=0.0f)
            {
                hit.indexList.push_back(&v0-first);
                hit.ratioList.push_back(r0);
            }

            if (r1!=0.0f)
            {
                hit.indexList.push_back(&v1-first);
                hit.ratioList.push_back(r1);
            }

            if (r2!=0.0f)
            {
                hit.indexList.push_back(&v2-first);
                hit.ratioList.push_back(r2);
            }
        }

        _settings->_lineSegIntersector->insertIntersection(hit);
        _hit = true;
    }

代码段1

为了分析该函数,就得写一个例子,让该例子的调用能进入到这个函数,从而实现断点调试,这样分析才高效、明白。 如下为测试用的例子代码:

#include <osgViewer/Viewer>
#include <osgUtil/IntersectionVisitor>
#include<osg/ShapeDrawable>
#include<iostream>
 
//创建盒子
osg::ref_ptr<osg::Geode> createBox()
{
    osg::ref_ptr<osg::Geode> geode1 = new osg::Geode;
    auto box1 = new osg::ShapeDrawable(new osg::Box(osg::Vec3(0.0, 0.0, 0.0), 10.0, 8.0, 6.0));
    geode1->addDrawable(box1);
 
    return geode1;
}
 
int main(int argc, char* argv[])
{
    osg::ref_ptr<osgViewer::Viewer> viewer1 = new osgViewer::Viewer;
    osg::ref_ptr<osg::Group> group1 = new osg::Group;
 
    osg::ref_ptr<osgUtil::LineSegmentIntersector> lineSegmentIntesector = new osgUtil::LineSegmentIntersector(osg::Vec3(0, 0, 15), osg::Vec3(0, 0, -15));
    osg::ref_ptr<osgUtil::IntersectionVisitor> intersectionVisitor1 = new osgUtil::IntersectionVisitor(lineSegmentIntesector);
 
    group1->addChild(createBox());
    group1->accept(*intersectionVisitor1.get());
 
    osgUtil::LineSegmentIntersector::Intersections intersections;
 
    //输出交点
    intersections = lineSegmentIntesector->getIntersections();
    osgUtil::LineSegmentIntersector::Intersections::iterator iter;
    for (iter = intersections.begin(); iter != intersections.end(); ++iter)
    {
        std::cout << "ratio:" << "   " << iter->ratio << "    x:" << iter->getWorldIntersectPoint().x() << "    y:" << iter->getWorldIntersectPoint().y() << "    z:" << iter->getWorldIntersectPoint().z() << std::endl;
    }
 
    viewer1->setSceneData(group1.get());
    return viewer1->run();
}

代码段2

代码段2构建了一条线段,线段起始坐标如下:

osg::Vec3(0, 0,15)

终点坐标如下:

osg::Vec3(0, 0, -15)

同时构建了一个长方体,长方体中心位于原点,长、宽、高、分别为:5、4、3。效果如下(注意:为了便于观察,我使这个长方体绕X轴逆时针转动了一定角度):

上述代码的第21、22行构造了一个求交器和求交访问器对象。通过求交器和求交访问器对象相互协同,再通过调用第25行代码accept函数,就能求出线段和长方体的交点。在代码段1设置断点,当启动该例子时,就能进入到intersect函数并停下来。代码段1的功能是求出线段和图元的交点信息。关于该段代码判断线段和面的交点及如何求出交点的算法,请参考:

射线和三角形的相交检测(ray triangle intersection test)

需要说明的是:虽然长方体6个面是四边形,但OPenGl是通过绘制两个三角形来实现一个四边形的。以长方体上顶面、下底面的四边形绘制为例说明如下:

通过在for循环中循环2次绘制两个三角形从而形成四边形(四边形由两个三角形拼接而成,在底层硬件实现上,大部分多边形的绘制是分割为三角形进行绘制的,这样效率要高些)。第1次循环绘制v0v2v1(或v0v3v2)三角形,此时检测到线段和该三角形交于A点;第2次循环绘制v0v3v2(或v0v2v1)三角形,此时又检测到线段和该三角形交于A点。

           在代码段1中_start表示线段起点被裁剪到最小外接包围盒的起点坐标,本例为osg::Vec3(0, 0,3)而 

_settings->_lineSegIntersector->getStart() 

则是线段原始起点坐标,本例为osg::Vec3(0, 0,15)。关于线段起点是如何转化为裁剪起始点的,请参考:osgUtil::LineSegmentIntersector类源码分析(二) 。对于知道线段的起点S和终点E,显然方向向量为D=E−S。这时,根据线段的向量方程,线段上某一点P为:

P = S + t*D

其中t的范围为[0, 1]。当t=0时是起点S;当t=1时是终点E;当t为(0,1)时是SE之间的一点。代码段1中t值的含义和这里的t意义相同,即表示位于_length之间的某点,而_length则表示线段裁剪起点到线段裁剪终点的长。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值