您好,欢迎来到意榕旅游网。
搜索
您的当前位置:首页OSG交运算、选取和碰撞

OSG交运算、选取和碰撞

来源:意榕旅游网

一、交运算与对象选取(osgUtil工具库)

1.计算几何基础算法

2.交运算与对象选取(osgUtil工具库)

osgUtil集合了场景图形处理、几何体修改工具及高层次的遍历等功能。

交运算——求交器osgUtil::Intersector

osgUtil::LineSegmentIntersector

  • 用于检测指定线段和场景图形之间的相交情况,并向程序提供查询相交测试结果的函数。
  • 提供了一种定义射线的方法(包含两个osg::Vec3,分别定义线段起点和终点),一般在拾取和显示位置的示例中会运用到。
    当交集测试被触发时(例如IntersectionVisitor),线段求交器执行相应操作并会保存检测线段的相交情况

osgViewer::View::computeIntersections

  • computeIntersections 方法用于根据用户指定的 2D 屏幕坐标计算与场景中几何体之间的交集。它在处理用户输入(如鼠标点击)时非常有用,可以确定用户点击了哪个物体。
  • 其中osgViewer::View::computeIntersections是对“创建LineSegmentIntersector和IntersectionVisitor,借助访问器遍历访问需要进行碰撞检测的节点,并进行求交”的封装。
// 创建一个线段交集检测对象
osgUtil::LineSegmentIntersector::Intersection intersection;// 用于存放
viewer->computeIntersections(x,y,intersection);// x,y可通过osgGA::GUIEventAdapter获得
// 通过相交运算,更多的是希望得到相交的点,可以通过申请一个迭代器来实现:
for(osgUitl::LineSegmentIntersector::Intersections ::iterator it = intersection.begin();it!=intersection.end();it++)
{
	...
	it->getWorldIntersectPoint().....
	...
}

osgUtil::PolytopeIntersector(源码解析后补)

  • 用于检测由一系列平面构成的多面体和场景图形之间的相交运算。例如:当鼠标单击场景图形中某一区域,希望拾取(求交)到鼠标位置附近的一个封闭多面体区域中的场景图形时。
  • ,建议配合阅读。
  • double distance :表示从当前这个交集的所有顶点的中心点到参考平面的距离.
  • Vec3_type localIntersectionPoint :表示所有交点的中心点
  • Vec3_type intersectionPoints[MaxNumIntesectionPoints] :所有交点的一个数组…目前最多的顶点个数是6… enum { MaxNumIntesectionPoints=6 };
  • unsigned int numIntersectionPoints :所有顶点的个数

osgUtil::PlaneIntersector

  • 用于检测由一系列平面构成的平面的相交运算。
  • 平面求交器的一个常用的使用场景是求与某些模型的交线,包括轮廓线、截面线(横断面、纵断面等).

二、osgUtil::IntersectionVisitor:osg::NodeVistor

1.概述

  • osgUtil::IntersectionVisitor对象使用osgUtil::Intersector派生对象作为其构建函数的参数。他可以配置为使用多种交集器进行相交测试,包括线段,面板与多边形。
  • 注意:求交时最好避开自身节点:setNodeMask(0x0),求交完,即accept之后再回复。
  • 一个交集器可以作用在四种类型的坐标系统中,其中的每一个都具有不同的输入参数,并且使用不同的转换矩阵将其转换为世界空间。

例如可以获取一条由屏幕鼠标位置到3D场景的光线

  • 求交器的containsIntersections()方法可以被用来检测是否存在交集结果,getIntersections方法返回一个Intersection集合变量,依据距离查看器的远近进行排序。
  • 某些求交器的Intersection包含特殊接口,例如osgUtil::LineSegmentIntersector::Intersection::getLocalIntersectPoint / getWorlIntersectPoint
osg::ref_ptr<osgUtil::LineSegmentIntersector> intersector =
    new osgUtil::LineSegmentIntersector(
        osgUtil::Intersector::WINDOW, ea.getX(), ea.getY()
    );
osgUtil::IntersectionVisitor iv( intersector.get() );
camera->accept( iv );

osgUtil::LineSegmentIntersector::Intersection& result =
    *( intersector->getIntersections().begin());
osg::Vec3 point = result.getWorldIntersectPoint();  // in world space
  • 触发机制和NodeVisitor相似,该访问器需要维护 / 自行创建 一个用于进行交集测试的线段列表,而对于其中的每一个线段,访问器都会创建一个交点列表(osgUtil::IntersectorVisitor::HitList实例,因为一条线段可能和多个几何体实例相交),该交点列表主要用于搜索场景图形中与指定几何体相交的交点,且相交测试的工作实际上是在osgUtil::Intersector的继承类中完成的。

IintersectVisitor :弃用,被IntersectionVisitor替代

  • 维护一个进行交集测试的线段列表,而对于其中的每一条线段,访问器都会创建一个交点列表(osgUtil::IntersectVisitor::HintList实例),用于搜索场景图形中与指定几何体相交的节点.
  • 交点列表osgUtil::IntersectVisitor::HintList的作用为:一条单一的线段可能与场景中的多个几何体实例(或多次与同一个几何体)产生交集。对于每一条参与交集测试的线段,系统均会产生一个列表,包含了所有交集测试产生的Hit实例。

而最后相交测试的工作将在osgUtil::Intersector的继承类(线段求交器、多面体求交器等)中完成。

2.Intersection

每个求交器可能有些许不同

  • osg::NodePath nodePath 父节点路径,表示一条路径:表示这个从根结点到当前这个geode的路径
  • 在vistor中我们有pushNodepath() popNodePath()来保存这个路径操作…所以这个路径是从vistor中获得的。
  • osg::computeLocalToWorld(nodePath );可以获得从该节点局部坐标系统到世界坐标系统的变换矩阵。

  • osg::ref_ptr< osg::Drawable > drawable :保存着当前这个交集是对于哪个drawable,即相交 / 命中 的几何体。
  • osg::ref_ptr< osg::RefMatrix > matrix :变换矩阵,表示当前这个drawable变换到到世界坐标系的变换矩阵。我们可以使用point*matrix 来得到获得点或节点等在世界坐标系下的位置。
  • unsigned int primitiveIndex :和线段相交的可绘制对象drawable ,也即几何对象图元索引(依赖于 Geometry 中数据的布局方式)。如:线段和长方体相交,则primitiveIndex就表示长方体的第几个三角面,注意:不是矩形面,而是三角面。
#include <osgUtil/PolytopeIntersector>
#include <osgGeometry>

void processIntersection(const osgUtil::PolytopeIntersector::Intersection& intersection) {
    osg::Drawable* drawable = intersection.drawable.get();
    osg::Geometry* geometry = drawable ? drawable->asGeometry() : nullptr;
    
    if (geometry) {
        osg::Vec3Array* vertices = dynamic_cast<osg::Vec3Array*>(geometry->getVertexArray());
        if (vertices) {
            // 假设每个基本体(例如三角形)由三个连续的顶点组成
            unsigned int startIdx = intersection.primitiveIndex * 3;// 即primitiveIndex表示图元的索引

            if (startIdx + 2 < vertices->size()) {
                osg::Vec3 v0 = (*vertices)[startIdx];
                osg::Vec3 v1 = (*vertices)[startIdx + 1];
                osg::Vec3 v2 = (*vertices)[startIdx + 2];
				// 输出相交的图元的顶点数据
                std::cout << "Intersected Primitive: " << std::endl;
                std::cout << "Vertex 0: (" << v0.x() << ", " << v0.y() << ", " << v0.z() << ")" << std::endl;
                std::cout << "Vertex 1: (" << v1.x() << ", " << v1.y() << ", " << v1.z() << ")" << std::endl;
                std::cout << "Vertex 2: (" << v2.x() << ", " << v2.y() << ", " << v2.z() << ")" << std::endl;
            }
        }
    }
}

如果使用了索引缓冲(osg::DrawElements,例如osg::DrawElementsUInt保存的是一系列索引),则 primitiveIndex 可能指向的是索引缓冲中的位置(从而获得索引值),而非直接的顶点数组索引。如下:

#include <osgUtil/PolytopeIntersector>
#include <osg/Geometry>

void processIndexedIntersection(const osgUtil::PolytopeIntersector::Intersection& intersection) {
    osg::Drawable* drawable = intersection.drawable.get();
    osg::Geometry* geometry = drawable ? drawable->asGeometry() : nullptr;

    if (geometry) {
        osg::Vec3Array* vertices = dynamic_cast<osg::Vec3Array*>(geometry->getVertexArray());
        if (!vertices) return;

        // 遍历每个 PrimitiveSet 以找到正确的 DrawElements
        for(unsigned int i = 0; i < geometry->getNumPrimitiveSets(); ++i) {
            osg::PrimitiveSet* primSet = geometry->getPrimitiveSet(i);

            if (auto* drawElements = dynamic_cast<osg::DrawElementsUInt*>(primSet)) {
                // 计算出 startIdx 是如何划分的,比如 GL_TRIANGLES 需乘以3
                // 即primitiveIndex表示了索引数组中的位置
                unsigned int base = intersection.primitiveIndex * 3;

                if (base + 2 < drawElements->size()) {
                    // 由索引位置获取顶点索引值
                    unsigned int vi0 = drawElements->at(base);
                    unsigned int vi1 = drawElements->at(base + 1);
                    unsigned int vi2 = drawElements->at(base + 2);

                    // 用索引值查找具体的顶点
                    osg::Vec3 v0 = (*vertices)[vi0];
                    osg::Vec3 v1 = (*vertices)[vi1];
                    osg::Vec3 v2 = (*vertices)[vi2];

                    std::cout << "Intersected Indexed Primitive: " << std::endl;
                    std::cout << "Vertex 0: (" << v0.x() << ", " << v0.y() << ", " << v0.z() << ")" << std::endl;
                    std::cout << "Vertex 1: (" << v1.x() << ", " << v1.y() << ", " << v1.z() << ")" << std::endl;
                    std::cout << "Vertex 2: (" << v2.x() << ", " << v2.y() << ", " << v2.z() << ")" << std::endl;
                }
                break; // Assuming a single PrimitiveSet contains the correct index, you can exit the loop
            }
        }
    }
}

3.简单碰撞测试示例

int main()
{
.......
	// 这里省略:定义viewer,group,和创建一个Box添加到group中,以及将group加入到场景

	
	osg::ref_ptr<osgUtil::LineSegmentIntersector> ls = new osgUtil::LineSegmentIntersector(osg::Vec3(0,0,15),osg::Vec3(0,0,-15));
	// 该访问器用来遍历group下面的成员
	osg::ref_ptr<osgUtil::IntersectionVisitor> iv = new osgUtil::IntersectionVisitor(ls);
	// 结果存放在osgUtil::LineSegmentIntersector::Intersections中
	osgUtil::LineSegmentIntersector::Intersections intersections;


	// 可以用camera,也可以用group启动访问器,因为启动函数是osg::Node accept()
	group->accept(*iv.get());// 会自动调用访问器中Apply、Enter、Leave等成员函数
	
	// 如果有碰撞,则输出所有的交点
	if(ls->containsIntersections())// 有交点即有碰撞,或者用hits()
	{
		intersections = ls->getIntersections();
		for(osgUitl::LineSegmentIntersector::Intersections ::iterator it = intersections.begin();it!=intersections.end();it++)
		{
			std::cout << it->getWorldIntersectPoint().x() <<"  "<<it->getWorldIntersectPoint().y()<<"  "<<it->getWorldIntersectPoint().z()<<std::endl;  
		}

	}

	viewer->run();
}

4.点击来选择空间中的一个对象并且在对象周围显示一个选取框

#include <osg/MatrixTransform>
#include <osg/ShapeDrawable>
#include <osg/PolygonMode>
#include <osgDB/ReadFile>
#include <osgUtil/LineSegmentIntersector>
#include <osgViewer/Viewer>
class PickHandler : public osgGA::GUIEventHandler
{
public:
    osg::Node* getOrCreateSelectionBox();
    virtual bool handle( const osgGA::GUIEventAdapter& ea,
                         osgGA::GUIActionAdapter& aa );

protected:
    osg::ref_ptr<osg::MatrixTransform> _selectionBox;
};

// 选取框,注意我们是靠matrixTransform来对框位置进行变换

osg::Node* PickHandler::getOrCreateSelectionBox()
{
    if ( !_selectionBox )
    {
        osg::ref_ptr<osg::Geode> geode = new osg::Geode;
        geode->addDrawable(
            new osg::ShapeDrawable(new osg::Box(osg::Vec3(), 1.0f)) );
        _selectionBox = new osg::MatrixTransform;
        _selectionBox->setNodeMask( 0x1 );// 求交器不会对该选取框进行判断!因为遍历器获得不了!
        _selectionBox->addChild( geode.get() );
        osg::StateSet* ss = _selectionBox->getOrCreateStateSet();
        ss->setMode( GL_LIGHTING, osg::StateAttribute::OFF );
        ss->setAttributeAndModes(new osg::PolygonMode(osg::PolygonMode::FRONT_AND_BACK,osg::PolygonMode::LINE));
    }
    return _selectionBox.get();
}

// 选取响应事件

bool PickHandler::handle( const osgGA::GUIEventAdapter& ea,osgGA::GUIActionAdapter& aa )
{
    if ( ea.getEventType()!=osgGA::GUIEventAdapter::RELEASE ||
         ea.getButton()!=osgGA::GUIEventAdapter::LEFT_MOUSE_BUTTON ||
         !(ea.getModKeyMask()&osgGA::GUIEventAdapter::MODKEY_CTRL))
        return false;
        
    osgViewer::Viewer* viewer = dynamic_cast<osgViewer::Viewer*>(&aa);
    if ( viewer )
    {
        osg::ref_ptr<osgUtil::LineSegmentIntersector>
            intersector =
            new osgUtil::LineSegmentIntersector(
                osgUtil::Intersector::WINDOW, ea.getX(), ea.getY()
            );
        osgUtil::IntersectionVisitor iv( intersector.get() );
        iv.setTraversalMask( ~0x1 );// 并不会获得自定义选取框,即求交的时候不会对选取框进行判断!
        viewer->getCamera()->accept( iv );

        if ( intersector->containsIntersections() )
        {
            osgUtil::LineSegmentIntersector::Intersection& result =
                *(intersector->getIntersections().begin());

			// 获得求交成功的几何体
            osg::BoundingBox bb = result.drawable->getBound();

			// 从根节点到求交成功的几何体的父节点路径获得该求交成功的几何体从局部坐标系统到世界坐标系统的变换矩阵。
            osg::Vec3 worldCenter = bb.center() *
                osg::computeLocalToWorld(result.nodePath);
            _selectionBox->setMatrix(
                osg::Matrix::scale(bb.xMax()-bb.xMin(),
                                  bb.yMax()-bb.yMin(),
                                  bb.zMax()-bb.zMin()) *
                osg::Matrix::translate(worldCenter) );
        }
    }
    return false;
}

// 启用

osg::ref_ptr<osg::Node> model1 = osgDB::readNodeFile( "cessna.osg"
);
osg::ref_ptr<osg::Node> model2 = osgDB::readNodeFile( "cow.osg" );
osg::ref_ptr<osg::Group> root = new osg::Group;
root->addChild( model1.get() );
root->addChild( model2.get() );

osg::ref_ptr<PickHandler> picker = new PickHandler;
root->addChild( picker->getOrCreateSelectionBox() );

osgViewer::Viewer viewer;
viewer.setSceneData( root.get() );
viewer.addEventHandler( picker.get() );// 在后续仿真循环中,上面添加的picker->getOrCreateSelectionBox()将动态变换。
return viewer.run();

某个节点及其子场景图对相交访问器不可访问

  • osg::NodeVisitor类的setTraversalMask()和osg::Node类的setNodeMask()搭配使用可以使得某个节点及其子场景图对相交访问器不可访问,只需要使得逻辑与的结果为零。
_selectionBox->setNodeMask( 0x1 );
iv.setTraversalMask( ~0x1 );

osg::computeLocalToWorld

  • 用于计算从场景图的某个节点到根节点的变换矩阵。
  • 该函数根据提供的节点路径(osg::NodePath)计算从该节点局部坐标系统到世界坐标系统的变换矩阵。这个过程涉及沿节点路径向上遍历,并累积每个节点的变换,从而形成从当前节点到场景根的完整变换。

因篇幅问题不能全部显示,请点此查看更多更全内容

Copyright © 2019- yrrf.cn 版权所有 赣ICP备2024042794号-2

违法及侵权请联系:TEL:199 1889 7713 E-MAIL:2724546146@qq.com

本站由北京市万商天勤律师事务所王兴未律师提供法律服务