目录
1.概述
osgUtil::Intersector有几个子类,如下:
每个子类表示不同的求交器。所谓求交器就是判定和物体相交的类,通过这些类可以很方便的得出交点、实现拾取功能等。LineSegmentIntersector类是osgUtil::Intersector其中的一个子类,其表示线段求交器,即通过线段和三维场景中的某个物体相交,该类一般和osgUtil::IntersectionVisitor即求交访问器类一起使用,从而得出交点、实现拾取功能等。关于osgUtil::LineSegmentIntersector类的具体应用,请参考如下博文:
LineSegmentIntersector::Intersections中ratio含义及LineSegmentIntersector相交点说明。
2.代码环境说明
环境说明如下:
- OpenSceneGraph-3.6.2。
- Windows 10。
- Microsoft Visual Studio Community 2022 (64 位) - Current版本 17.5.5。
说明:本博文是基于OpenSceneGraph的3.6.2版本来讲解的,读者版本可能和本人的版本不同,
故本人的源码或功能可能在细节上和读者的有所不同。
3.intersects函数分析
intersects函数有好几个,本节讲解的intersects函数源码如下:
bool LineSegmentIntersector::intersects(const osg::BoundingSphere& bs)
{
// if bs not valid then return true based on the assumption that an invalid sphere is yet to be defined.
if (!bs.valid()) return true;
osg::Vec3d sm = _start - bs._center;
double c = sm.length2()-bs._radius*bs._radius;
if (c<0.0) return true;
osg::Vec3d se = _end-_start;
double a = se.length2();
double b = (sm*se)*2.0;
double d = b*b-4.0*a*c;
if (d<0.0) return false;
d = sqrt(d);
double div = 1.0/(2.0*a);
double r1 = (-b-d)*div;
double r2 = (-b+d)*div;
if (r1<=0.0 && r2<=0.0) return false;
if (r1>=1.0 && r2>=1.0) return false;
if (_intersectionLimit == LIMIT_NEAREST && !getIntersections().empty())
{
double ratio = (sm.length() - bs._radius) / sqrt(a);
if (ratio >= getIntersections().begin()->ratio) return false;
}
// passed all the rejection tests so line must intersect bounding sphere, return true.
return true;
}
代码段1
为了分析该函数,就得写一个例子,让该例子的调用能进入到这个函数,从而实现断点调试,这样分析才高效、明白。 下面是摘自1节提到的博文中的例子,代码如下:
#include <osgViewer/Viewer>
#include <osgUtil/IntersectionVisitor>
#include <osgUtil/LineSegmentIntersector>
#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);
// geode1->addDrawable(new osg::ShapeDrawable(new osg::Box(osg::Vec3(0.0, 0.0, 0.0), 0.1, 0.1, 20)));
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());
viewer1->setUpViewInWindow(200, 200, 800, 600, 0);
return viewer1->run();
}
代码段2
上述代码构建了一条线段,线段起始坐标如下:
osg::Vec3(0, 0,15)
终点坐标如下:
osg::Vec3(0, 0, -15)
同时构建了一个长方体,长方体中心位于原点,长、宽、高、分别为:5、4、3。效果如下(注意:为了便于观察,我使这个长方体绕X轴逆时针转动了一定角度):
上述代码的第23、24行构造了一个求交器和求交访问器对象。通过求交器和求交访问器对象相互协同,再通过调用第27行代码accept函数,就能求出线段和长方体的交点。在代码段1设置断点,当启动该例子时,就能进入到intersects函数并停下来。代码段1的功能是检测线段是否和三维场景中某个物体的最小外接球有交点。
3.1.代码分析
代码段1中的第4行:如果包围这个长方体的最小外接球是无效的(外接球半径<0),则直接返回true。因为如果最小外接球都无效,则本函数后面的逻辑处理都没意义。
代码段1中的第6~8行:算出长方体最小外接球球心指向线段起点的向量sm,并计算sm的长度即sm的模是否比外接球半径小,如果是,则证明线段的起点在外接球球内,则返回true,即该线段必定和外接球相交(注:要把外接球看为实心球体)。
代码段1中的第10~26行:这几行代码是线段起点在球外面时,判断线段是否和球相交。这段代码就是求直线和球的交点,有三种可能,要么相切有一个交点,要么相交有两个交点,否则的话没有交点,这也符合空间直线与球面相交的直观认识,。 关于直线和球的交点算法,请参考:
上述博文是求直线和球交点,但在计算机中是无法直接表示无限长的直线的,在计算机中,一般用有范围的线段来表示直线,如下代码:
if (r1<=0.0 && r2<=0.0) return false;
r1、r2虽然是表示直线和球的交点,但r1、r2点在线段的起始点前面,不是指定范围的线段内的点,故不是线段和球的交点。如下S表示线段的起点;E表示线段的终点,则r1、r2点在S点前面:
如下代码:
if (r1>=1.0 && r2>=1.0) return false;
r1、r2点在线段的终点后面,不是指定范围的线段内的点。如下:
如下图:
第30行表示的含义是:DS / SE的比值;根据几何知识我们知道DS是S点到球(圆)的最近距离。getIntersections().begin()->ratio表示从线段起点和球相交的第一个交点的长度占据整个线段长度的比例即SA / SE。那么代码段1中的第28~32行含义为:如果设定要找的交点为离球最近,如果线段起点和第一个交点的长度(SA )比线段起点到球的最近距离(DS)还小,则这样的交点是不存在的,因为DS是离球面最小的。存在的问题:本人觉得此处的等号不能要,即第31行要改为:
if (ratio > getIntersections().begin()->ratio) return false;
因为如下两种情况都是可以找到符合和球最近距离的交点的:
此种情况是线段SE和球相切,起点S就是第一个和球的交点。
此种情况是线段SE和球相交,但起点S就是第一个和球的交点。
4. 附录
关于该类的更多知识,可参考: