【实践OsgRecipes项目】基础篇一

0、环境配置和简介

OSG要配置的库还挺多的,自己编译起来,说实话很麻烦,当时搞了很久,后面用的别人弄好的库,具体配置方法的链接放这里:环境搭建

OsgRecipes项目我是在Windows,用cmake-gui编译的,当时也是挺多问题的,后面是问AI一个个解决的。

这个OsgRecipes项目用到的第三方库很多,目前还在探索中,其实本人也刚接触OSG不久,感觉这个项目会比较有意思,所以记录一下思路和问题,输出倒逼自己输入,同时也算是边玩边学~

编译好后,打开解决方案,会发现里面都是cookbook(循序渐进的感觉,有点像LearnOpenGL,感兴趣的话可以看下,我之前实践这个项目的文章LearnOpenGL),我去搜了下,为啥叫cookbook菜谱,其实也算是去烹饪去使用OSG这个菜系的方法,从而做出自己想要的菜,想要的效果

1、cookbook_02_01代码解析

其实很多程序运行起来都是全屏的,很不方便调试,这里我们改一下窗口的大小,首先在类PickHandler添加一个bool成员变量m_first,表示是否第一次设置大小,避免重复设置。具体代码如下

if (m_first)
{
	m_first = false;
	osgViewer::Viewer* viewer = dynamic_cast<osgViewer::Viewer*>(&aa);  //获取窗口句柄
	osgViewer::Viewer::Windows ws;
	viewer->getWindows(ws);
	if (!ws.empty())
	{
		osgViewer::Viewer::Windows::iterator iter = ws.begin();
		for (; iter != ws.end(); iter++)    //遍历所有关联窗口设置大小
		{
			(*iter)->setWindowRectangle(500, 100, 900, 800);
		}
	}
}

欧克,先简要分析下observer_ptr.cpp的main函数,主要出现了五六种类。

首先类osg::Geode,它就像画布,比如这里在osg::Geode画布上添加了osgText::Text文本元素,以及两个几何体。

然后类osg::Camera,顾名思义决定了我们能看到的东西,这里的话是创建了一个平视显示(HUD)相机,用来显示文本对象的。

osg::Group的话,就像电脑中的文件夹,可以包含其他的Group和Geode,就像文件夹包含子文件夹和文件。而类ObserveShapeCallback继承的NodeCallback类似于监控摄像头,物体变化时用来实时响应,这里的话文本是和几何体建立了联系。

osgViewer::Viewer就好比是电影院的放映机,负责协调所有组件并且将最终画面呈现出来,管理着渲染循环和事件处理等功能。

然后看下具体创建文本的函数,代码如下

osgText::Text* createText( const osg::Vec3& pos, const std::string& content, float size )
{
    osg::ref_ptr<osgText::Text> text = new osgText::Text;
    text->setDataVariance( osg::Object::DYNAMIC );  //变化模式设为动态
    text->setFont( g_font.get() );
    text->setCharacterSize( size );
    text->setAxisAlignment( osgText::TextBase::XY_PLANE );
    text->setPosition( pos );
    text->setText( content );
    return text.release();
}

其实这里的指针osg::ref_ptr类似于STL的std::shared_ptr,是强引用会自动计数,而后面将会出现的指针osg::observer_ptr是弱引用,仅观察不会计数,类似于STL的std::weak_ptr。然后看下创建HUD相机的函数

osg::Camera* createHUDCamera( double left, double right, double bottom, double top )
{
    osg::ref_ptr<osg::Camera> camera = new osg::Camera;
    camera->setReferenceFrame( osg::Transform::ABSOLUTE_RF );   //使用绝对坐标系,不受父节点变换影响
    camera->setClearMask( GL_DEPTH_BUFFER_BIT );
    camera->setRenderOrder( osg::Camera::POST_RENDER ); //后渲染,覆盖在主场景上
    camera->setAllowEventFocus( false );
    camera->setProjectionMatrix( osg::Matrix::ortho2D(left, right, bottom, top) );  //2D视口范围
    camera->getOrCreateStateSet()->setMode( GL_LIGHTING, osg::StateAttribute::OFF );
    return camera.release();
}

这里的left, right, bottom, top对应的数据,一开始会让我比较费解,这个其实是将这里设置的矩形视口范围映射到标准设备坐标(NDC)中,比如这里的

(0,800,0,600)会将x∈[0,800]映射到NDC的[-1,1],y∈[0,600]同理。因此设置里面的文本对象位置时,setPosition的参数或者文本位置这些是和(0,800,0,600)有很大关联的

 接下来我们看下ObserveShapeCallback类,主要代码如下

virtual void operator()( osg::Node* node, osg::NodeVisitor* nv )
{
    std::string content;
    if ( _drawable1.valid() ) content += "Drawable 1; ";
    if ( _drawable2.valid() ) content += "Drawable 2; ";
    if ( _text.valid() ) _text->setText( content );
    traverse(node, nv);
}

除了对文本进行一个赋值外,里面的traverse(node, nv)在这里是可以省略的,因为它是用来遍历子节点的,而这里只是修改了当前节点的文本内容

我们再看下事件类RemoveShapeHandler,首先看下它的基类PickHandler,它的基类是osgGA::GUIEventHandler,如同OSG的场控系统。看下虚函数handle的具体实现,主要代码如下

 //同时按住ctrl键和鼠标左键,释放时触发
if ( ea.getEventType()!=osgGA::GUIEventAdapter::RELEASE ||
     ea.getButton()!=osgGA::GUIEventAdapter::LEFT_MOUSE_BUTTON ||
     !(ea.getModKeyMask()&osgGA::GUIEventAdapter::MODKEY_CTRL) )   
    return false;


osgViewer::View* viewer = dynamic_cast<osgViewer::View*>(&aa);
if ( viewer )
{
    osg::ref_ptr<osgUtil::LineSegmentIntersector> intersector =
        new osgUtil::LineSegmentIntersector(osgUtil::Intersector::WINDOW, ea.getX(), ea.getY());
    osgUtil::IntersectionVisitor iv( intersector.get() );
    viewer->getCamera()->accept( iv );  //通过访问器模式,执行鼠标射线碰撞检测
    
    if ( intersector->containsIntersections() )
    {
        //获取首个命中结果
        osgUtil::LineSegmentIntersector::Intersection result = *(intersector->getIntersections().begin());
        doUserOperations( result );
    }
}

这里实现的事件是同时按住CTRL键和鼠标左键,松掉时会触发后续的碰撞检测:鼠标射线和物体的是否存在交集,若存在则调用子类的doUserOperations。紧接着,我们看下其子类RemoveShapeHandler实现的doUserOperations方法

virtual void doUserOperations( osgUtil::LineSegmentIntersector::Intersection& result )
{
    //输出信息,方便理解result.nodePath
	for (unsigned i = 0; i < result.nodePath.size(); ++i) {
		std::cout << "Node " << i << ": "
			<< result.nodePath[i]->className() << std::endl;
	}
    std::cout << "Hit Drawable: " << result.drawable->className() << std::endl;

    if ( result.nodePath.size()>0 ) //场景节点路径
    {
        osg::Geode* geode = dynamic_cast<osg::Geode*>( result.nodePath.back() );
        if ( geode ) geode->removeDrawable( result.drawable.get() );
    }
}

首先这里的输出有两种,如果点击几何体的话是,点击文本的话是。这是因为点击操作的起点,是当前渲染的视口相机节点,对应的Node0Node1的话对应的类osg::Group对应root,作为场景图的入口,后面便是实际点击到的叶节点,而文本会多一层,是因为它在HUD相机对象中。相应的,就能理解后续为什么可以移除掉点击的对象了~

2、cookbook_02_01项目拓展

后续想着既然文本和几何体是绑定在一起的,那能不能将文本修改为动态变化的?

于是就将固定的文本改成了相机视角中心与对应几何体中心的距离。主要修改下operator(),代码如下

 	virtual void operator()(osg::Node* node, osg::NodeVisitor* nv)
 	{
 		if (!_text || !_camera) return;
 
 		//获取相机视角中心的世界坐标
 		osg::Vec3 eye, center, up;
 		_camera->getViewMatrixAsLookAt(eye, center, up);
 
 		std::stringstream ss;
 		ss.precision(2);
 		ss.setf(std::ios::fixed);//定点表示法,而非科学计数法
 
 		float dist1 = 0.f, dist2 = 0.f;
 		if (_drawable1.valid())
 		{
 			osg::ShapeDrawable* shape1 = dynamic_cast<osg::ShapeDrawable*>(_drawable1.get());
 			if (shape1)
 			{
 				osg::Vec3 center1 = shape1->getBound().center();
 				dist1 = (center1 - center).length();    //欧式距离
 				ss << "Box距离: " << dist1 << "; ";
 			}
 		}
 
 		if (_drawable2.valid())
 		{
 			osg::ShapeDrawable* shape2 = dynamic_cast<osg::ShapeDrawable*>(_drawable2.get());
 			if (shape2)
 			{
 				osg::Vec3 center2 = shape2->getBound().center();
 				dist2 = (center2 - center).length();
 				ss << "距离: " << dist2;
 			}
 		}
 		osgText::Font* font = osgText::readFontFile("simhei.ttf");  //不设置的话,中文会乱码
 		if (font) {
 			_text->setFont(font);
 		}
 		std::wstringstream wss;
 		wss << L"立方体距离: " << dist1 << "; ";
 		wss << L"球体距离: " << dist2;
 
 		std::wstring ws = wss.str();
 		wchar_t* result = new wchar_t[ws.size() + 1];
 		wcscpy(result, ws.c_str());
 
 		_text->setText(result);
 		_text->setCharacterSize(20.0f);
 		_text->setBackdropType(osgText::Text::OUTLINE);
 
 		//traverse(node, nv);
 	}

其实一开始老是出现中文乱码的情况,后面把std::string的result换成wchar_t*,并且设置成simhei.ttf的字体才解决。最终效果如下

最后,水平有限,欢迎指正和交流~觉得有用的话点点赞或收藏吧~

喜欢的话,点点关注吧~ 作者会一直更新的!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值