OSG选取点云坐标不准的解决办法
一、默认的相机和所有模型求交的方式
1.1 传统的模型与屏幕点求交的方法如下:
1 osgViewer::View* viewer = dynamic_cast<osgViewer::View*>(&aa); 2 if ( viewer ) 3 { 4 osg::ref_ptr<osgUtil::LineSegmentIntersector> intersector = 5 new osgUtil::LineSegmentIntersector(osgUtil::Intersector::WINDOW, ea.getX(), ea.getY()); 6 osgUtil::IntersectionVisitor iv( intersector.get() ); 7 viewer->getCamera()->accept( iv ); 8 9 if ( intersector->containsIntersections() ) 10 { 11 osgUtil::LineSegmentIntersector::Intersection result = *(intersector->getIntersections().begin()); 12 doUserOperations( result ); 13 } 14 }
从上面可以知道通过点击屏幕上一点其实就是构造一根线,然后将这条线和场景中的模型进行碰撞检测,会生成一个结果集,通过遍历结果集就可以处理相交的点,默认结果集中第一个点就是最近的相交点
但是在osg和点云结合的程序中,通过线和点云求交是几乎没有交点的,所以我们需要重写与点云模型的求交器,下面的PointIntersector求交器就是派生自osgUtil::LineSegmentIntersector,然后重写求交逻辑的点云模型求交器,主要参考的就是OpenSceneGraph Cookbook,下面就是点云模型和屏幕点求交的用法:
1.2、OSG中与点云模型选点的方法:
1 osg::ref_ptr<PointIntersector> intersector = new PointIntersector(osgUtil::Intersector::WINDOW, ea.getX(), ea.getY()); 2 osgUtil::IntersectionVisitor iv(intersector.get()); 3 viewer->getCamera()->accept(iv); 4 5 if (intersector->containsIntersections()) 6 { 7 //osg::Vec3d worldpoint = CRealInteractionUtil::getNeartestPoint(intersector, osg::Vec2f(ea.getX(), ea.getY()), transformMatrix*vpw); 8 PointIntersector::Intersection result = *intersector->getIntersections().begin();
//....
9 }
以上通过osg里面重写的求交器可以与场景中的点云模型进行求交,然后求得相交点的坐标,求交的原理大概就是PointIntersector求交器首先构造一个管状空间,此空间参数可以设置,默认半径是两米,所以求交的结果集是屏幕点生成线,然后通过线扩充成圆柱,根据此圆柱和点云模型求得结果集,结果集会包括屏幕点附近的点,此时如何知道结果集中哪个点时认为认为的选中点呢?默认结果集中第一个结果就是最近的相交点
二、指定的模型节点和相机求交的方式
通过以上方式也可以求得和点云求交选中点的坐标,但如果是单独与osg中的点云节点求交,此时就不能通过屏幕点构造求交器了,原因如下:
camera中的点都是基于屏幕的二维坐标,用屏幕点构造求交器也是二维坐标,所以两者可以进行求交得出碰撞点
特定节点中的点可能是基于世界坐标系的三维点,所以求交器也得是世界坐标系的三维点来构造的,如果用屏幕点构造求交器去求交就会导致没结果,因为坐标系没有统一起来
所以需要先构造三维场景中的一个开始点和一个结束点构造求交器,如下:
1 osgViewer::View* viewer = dynamic_cast<osgViewer::View*>(&aa); 2 if (!viewer) 3 { 4 return false; 5 } 6 osg::Matrixd Matrix_V = viewer->getCamera()->getViewMatrix();//观察矩阵-将对象由世界坐标变换为像机坐标 7 osg::Matrixd Matrix_P = viewer->getCamera()->getProjectionMatrix();//投影矩阵-投影到屏幕 8 osg::Matrixd Matrix_W = viewer->getCamera()->getViewport()->computeWindowMatrix();//视口矩阵-将投影坐标变换到二维视口 9 osg::Matrixd vwp = Matrix_V*Matrix_P*Matrix_W; //求得 世界到屏幕的矩阵 10 osg::Matrixd inverseVPW = osg::Matrixd::inverse(vwp); 11 osg::Vec3d world_sta = osg::Vec3d(ea.getX(), ea.getY(), 0)*inverseVPW;//屏幕坐标转成世界坐标 12 osg::Vec3d world_end = osg::Vec3d(ea.getX(), ea.getY(), 1)*inverseVPW; 13 osg::ref_ptr<PointIntersector> intersector = new PointIntersector(world_sta, world_end); 14 osgUtil::IntersectionVisitor iv(intersector.get()); 15 m_pDataManagerSingleton->getVectorNode()->accept(iv); 16 if (!intersector->containsIntersections()) 17 { 18 return false; 19 } 20 osg::Vec3d worldpoint = CRealInteractionUtil::getNeartestPoint(intersector, osg::Vec2f(ea.getX(), ea.getY()), vwp); 21 PointIntersector::Intersection result = *(intersector->getIntersections().begin());
1 osg::Vec3d CRealInteractionUtil::getNeartestPoint(PointIntersector* intersector, osg::Vec2f& curScreenXY, osg::Matrixd& vpw) 2 { 3 vector<osg::Vec3d> points; 4 for (osgUtil::LineSegmentIntersector::Intersections::iterator iter = intersector->getIntersections().begin(); iter != intersector->getIntersections().end(); iter++) 5 { 6 points.push_back(iter->getWorldIntersectPoint()); 7 } 8 float distance = 10000; 9 osg::Vec3d curNeartestPoint; 10 for (int i = 0; i < points.size(); i++) 11 { 12 osg::Vec3d tempPoint = points[i] * vpw; 13 osg::Vec2f tempScreenXY = osg::Vec2f(tempPoint[0], tempPoint[1]); 14 float lenth = (tempScreenXY - curScreenXY).length2(); 15 if (lenth < distance) 16 { 17 distance = lenth; 18 curNeartestPoint = points[i]; 19 } 20 } 21 return curNeartestPoint; 22 }
以上代码就是通过三维中的一个开始点和一个结束点构造一个柱状空间与点云模型求交,此时会求出很多个相交的点结果,如果我们默认也认为结果集的第一个就是正确的点,那就错了,开始我也是如此使用的,但是始终觉得会选偏,就是选不准,返回的第一个点总是在屏幕点附近,而不是正中心的那个点,即使我把半径设为很小,还是会偏,因为求交的结果集是在一个圆柱里面的,都是杂乱无章的,我们是无法判断哪个是正确的那个点的,此时我们可以将结果集的三维点先投影到屏幕中,通过与屏幕点的点再次求一个最近点,此时这个点的三维坐标就是正确的点云求交点了