osg用osgUtil库中的LineSegmentIntersector、IntersectionVisitor类来求线段和三维模型的交点
如下代码:
#include <QtCore/QCoreApplication>
#include <osgViewer/Viewer>
#include <osgViewer/ViewerEventHandlers>
#include <osgViewer/CompositeViewer>
#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[])
{
QCoreApplication a(argc, 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();
}
上述代码构建了一条线段,线段起始坐标如下:
Vec3(0, 0,15)
终点坐标如下:
osg::Vec3(0, 0, -15)
同时构建了一个长方体,长方体中心位于原点,长、宽、高、分别为:5、4、3。
效果如下(注意:为了便于观察,我使这个长方体绕X轴逆时针转动了一定角度):
图1
上述代码的第27、28行构造了一个求交器和求交访问器对象。通过求交器和求交访问器对象相互协同合作,再通过调用第31行代码accept函数,就能求出线段和长方体的交点,结果如下:
图2
其中ratio含义如下:
( Ls_z - Is_z ) / Line_Z_Length
其中Ls_z表示线段起始点的z坐标值;Is_z表示线段和长方体相交时,长方体在交点处的z坐标;Line_Z_Length表示线段在Z轴的总长度。上例子,线段起点z坐标为15,线段在Z轴的总长度为15 -(-15) = 30,由图2知道,第1个交点的长方体z坐标为3,则第1个ratio为:
( 15 - 3 ) / 30 = 0.4
同样地,可以算出最后一个交点的ratio为:
[ 15 - (-3 )] / 30 = 0.6
当把第27行线段的起始点、终止点坐标换成如下代码:
osg::ref_ptr<osgUtil::LineSegmentIntersector> lineSegmentIntesector = new osgUtil::LineSegmentIntersector(osg::Vec3(1, 5, 2), osg::Vec3(15, 0, -2));
则结果如下:
图3
则根据上面提到的计算ratio方法,线段起点z坐标为2,线段在Z轴的总长度为2 -(-2) = 4,由图3知道,第1个交点的长方体z坐标为1.2,则第1个ratio为:
(2 - 1.2) / 4.0 = 0.2
在平时的开发中,可以根据ratio知道交点占据整个z轴的比例,即占据厚度的比例。同理,第2个ratio为:
( 2 - 0.857143 ) / 4 = 0.285714
在平时的开发中,可以根据ratio知道交点占据整个z轴的比例,即占据厚度的比例。
在图2中,可以看到第1、2个交点是同一个点;第3、4个交点也是同一个点,为何出现两个相同的交点?下面分析如下:
通过断点跟进到底层源码发现:虽然长方体6个面是四边形,但OPenGl是通过绘制两个三角形来实现一个四边形的。即长方体上顶面、下底面的四边形绘制如下:
图4
通过在for循环中循环2次绘制两个三角形从而形成四边形(四边形由两个三角形拼接而成,在底层硬件实现上,大部分多边形的绘制是分割为三角形进行绘制的,这样效率要高些)。第1次循环绘制v0v1v2(或v0v2v3)三角形,此时检测到线段和该三角形交于A点;第2次循环绘制v0v2v3(或v0v1v2)三角形,此时检测到线段和该三角形交于A点,这就是上面交点被输出两次的原因。
如果把上面代码的第13行改为如下:
auto box1 = new osg::ShapeDrawable(new osg::Box(osg::Vec3(0.0, 0.0, 0.0), 0.1, 0.1, 20))
即把长方体宽、高尽量小,则结果如下(注意:为了便于观察,我使这个长方体绕X轴逆时针转动了一定角度):
图5
可以看到,长方体上顶面和直线的交点只有一个,下底面和直线的交点符合前文提到的三角形描述,输出2个相同的交点值,长方体上顶面和下底面都平行于水平面,应该说上顶面和下底面没啥区别,都应该输出2个相同的交点值才行,跟踪到LineSegmentIntersector.cpp文件的intersect函数:
void intersect(const osg::Vec3& v0, const osg::Vec3& v1, const osg::Vec3& v2)
{
....... // 其它代码略
if ((u + v) > det)
{
return; // 上顶面本来应该输出2个相同交点,但有一个三角形检测,从这里返回了
}
....... // 其它代码略
}
上顶面本来应该输出2个相同交点,但上顶面有一个三角形检测时,从return返回了,导致交点没插入交点容器,里面的算法不是很懂,希望懂的人留言交流,感谢了!。
通过测试发现,只要长方体长、宽、高小于1时,都会存在这个问题。