osgUtil::LineSegmentIntersector类源码分析(二)

目录

1.概述

2.代码环境说明

3.intersectAndClip函数分析

3.1.代码分析


1.概述

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

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

2.代码环境说明

环境说明如下:

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

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

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

3.intersectAndClip函数分析

    本节讲解的intersectAndClip函数源码如下:

bool LineSegmentIntersector::intersectAndClip(osg::Vec3d& s, osg::Vec3d& e,const osg::BoundingBox& bbInput)
{
    osg::Vec3d bb_min(bbInput._min);
    osg::Vec3d bb_max(bbInput._max);

    double epsilon = 1e-5;

    // compare s and e against the xMin to xMax range of bb.
    if (s.x()<=e.x())
    {
        // trivial reject of segment wholely outside.
        if (e.x()<bb_min.x()) return false;
        if (s.x()>bb_max.x()) return false;

        if (s.x()<bb_min.x())
        {
            // clip s to xMin.
            double r = (bb_min.x()-s.x())/(e.x()-s.x()) - epsilon;
            if (r>0.0) s = s + (e-s)*r;
        }

        if (e.x()>bb_max.x())
        {
            // clip e to xMax.
            double r = (bb_max.x()-s.x())/(e.x()-s.x()) + epsilon;
            if (r<1.0) e = s+(e-s)*r;
        }
    }
    else
    {
        if (s.x()<bb_min.x()) return false;
        if (e.x()>bb_max.x()) return false;

        if (e.x()<bb_min.x())
        {
            // clip e to xMin.
            double r = (bb_min.x()-e.x())/(s.x()-e.x()) - epsilon;
            if (r>0.0) e = e + (s-e)*r;
        }

        if (s.x()>bb_max.x())
        {
            // clip s to xMax.
            double r = (bb_max.x()-e.x())/(s.x()-e.x()) + epsilon;
            if (r<1.0) s = e + (s-e)*r;
        }
    }

    // compare s and e against the yMin to yMax range of bb.
    if (s.y()<=e.y())
    {
        // trivial reject of segment wholely outside.
        if (e.y()<bb_min.y()) return false;
        if (s.y()>bb_max.y()) return false;

        if (s.y()<bb_min.y())
        {
            // clip s to yMin.
            double r = (bb_min.y()-s.y())/(e.y()-s.y()) - epsilon;
            if (r>0.0) s = s + (e-s)*r;
        }

        if (e.y()>bb_max.y())
        {
            // clip e to yMax.
            double r = (bb_max.y()-s.y())/(e.y()-s.y()) + epsilon;
            if (r<1.0) e = s+(e-s)*r;
        }
    }
    else
    {
        if (s.y()<bb_min.y()) return false;
        if (e.y()>bb_max.y()) return false;

        if (e.y()<bb_min.y())
        {
            // clip e to yMin.
            double r = (bb_min.y()-e.y())/(s.y()-e.y()) - epsilon;
            if (r>0.0) e = e + (s-e)*r;
        }

        if (s.y()>bb_max.y())
        {
            // clip s to yMax.
            double r = (bb_max.y()-e.y())/(s.y()-e.y()) + epsilon;
            if (r<1.0) s = e + (s-e)*r;
        }
    }

    // compare s and e against the zMin to zMax range of bb.
    if (s.z()<=e.z())
    {
        // trivial reject of segment wholely outside.
        if (e.z()<bb_min.z()) return false;
        if (s.z()>bb_max.z()) return false;

        if (s.z()<bb_min.z())
        {
            // clip s to zMin.
            double r = (bb_min.z()-s.z())/(e.z()-s.z()) - epsilon;
            if (r>0.0) s = s + (e-s)*r;
        }

        if (e.z()>bb_max.z())
        {
            // clip e to zMax.
            double r = (bb_max.z()-s.z())/(e.z()-s.z()) + epsilon;
            if (r<1.0) e = s+(e-s)*r;
        }
    }
    else
    {
        if (s.z()<bb_min.z()) return false;
        if (e.z()>bb_max.z()) return false;

        if (e.z()<bb_min.z())
        {
            // clip e to zMin.
            double r = (bb_min.z()-e.z())/(s.z()-e.z()) - epsilon;
            if (r>0.0) e = e + (s-e)*r;
        }

        if (s.z()>bb_max.z())
        {
            // clip s to zMax.
            double r = (bb_max.z()-e.z())/(s.z()-e.z()) + epsilon;
            if (r<1.0) s = e + (s-e)*r;
        }
    }

    // OSG_NOTICE<<"clampped segment "<<s<<" "<<e<<std::endl;

    // if (s==e) return false;

    return 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();
}

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

osg::Vec3(0, 0,15)

终点坐标如下:

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

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

上述代码的第21、22行构造了一个求交器和求交访问器对象。通过求交器和求交访问器对象相互协同,再通过调用第25行代码accept函数,就能求出线段和长方体的交点。在代码段1设置断点,当启动该例子时,就能进入到intersectAndClip函数并停下来。代码段1的功能是将线段的起点、终点裁剪到被碰撞检测物体的最小包围盒范围内。

3.1.代码分析

第9~47行:将线段起点、终点的X坐标都裁剪到最小外接包围盒的[bb_min.x(), bb_max.x()]范围。

第50~88行:将线段起点、终点的Y坐标都裁剪到最小外接包围盒的[bb_min.y(), bb_max.y()]范围。

第91~129行:将线段起点、终点的Z坐标都裁剪到最小外接包围盒的[bb_min.z(), bb_max.z()]范围。

下面以X坐标裁剪为例子讲解说明,Y、Z坐标和X坐标思想相同,不再说明。

第9~28行:处理线段起始点的X坐标比终点的X坐标小或相等情况。 

第12~13行:在起始点的X坐标比终点的X坐标小或相等情况下,如果线段终点比最小外接包围盒的bb_min.x()还小,则显然该直线不可能会和包围盒有交点;同样地,在起始点的X坐标比终点的X坐标小或相等情况下,如果线段起点比最小外接包围盒的bb_max.x()还大,则显然该直线也不可能会和包围盒有交点。

第15~27行:在起始点的X坐标比终点的X坐标小或相等情况下且排除了不可能相交的两种情况后,程序就开始判断相交的情况了。这几行处理如下图所示的情况:

即起始点的x坐标小于bb_min.x()即始点S点在平面ADHE左侧且终点E的x坐标大于bb_max.x()即E点在平面BCGF右侧的情况。

第18行:本行代码算出起始点S.x到bb_min.x()的距离占整条线段X轴方向上的比重。

第19行:既然第18行算出了起始点S.x到bb_min.x()的距离占整条线段X轴方向上的比重,根据线性关系,则线段SE的起点S裁剪到平面ADHE就为:

s = s + (e-s)*r

同理,SE的终点E裁剪到平面BCGF为:

e = s+(e-s)*r;

说明:epsilon是了进行浮点比较,考虑到计算机的精度,对于double类型的数值和0的比较一般不是和0直接进行相等比较,即不是用下面那样:

double a = 0.0;
if(a == 0.0) 
  ....... // 期待代码略

而是:

double a = 0.0;
if(std::absf(a) < 1e-5) 
  ....... // 期待代码略

减去epsilon或加上epsilon表示S点在平面ADHE或E点平面BCGF。

  • 14
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值