目录
1. 概述
osgUtil::Intersector有几个子类,如下:
每个子类表示不同的求交器。所谓求交器就是判定和物体相交的类,通过这些类可以很方便的得出交点、实现拾取功能等。LineSegmentIntersector类是osgUtil::Intersector其中的一个子类,其表示线段求交器,即通过线段和三维场景中的某个物体相交,该类一般和osgUtil::IntersectionVisitor即求交访问器类一起使用,从而得出交点、实现拾取功能等。关于osgUtil::LineSegmentIntersector类的具体应用及源码分析,请参考如下博文:
- LineSegmentIntersector::Intersections中ratio含义及LineSegmentIntersector相交点说明
- osgUtil::LineSegmentIntersector类源码分析(一)
- osgUtil::LineSegmentIntersector类源码分析(二)
- LineSegmentIntersector::Intersection结构体各成员含义
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则表示线段裁剪起点到线段裁剪终点的长。
文章评论