<7> 场景图形的工作机制 - 1

        本章主要介绍 OSG 场景的工作机制以及如何动态变更场景数据,主要内容包括内存管理、访问器、回调的设计及数据变量。

1. 内存管理

        面对大规模的场景时,经常需要增加或删除一些数据,这些操作往往会因为不小心造成内存泄露或野指针,从而导致系统崩溃。一个熟练的开发人员也不能保证能够实时、正确地释放已经不用的内存空间。作为初学者,这更是一个非常难的问题。在OSG中,提供了一种新的机制——智能指针。智能指针(Smart pointer)是一种类的模板,它针对某一特定类型的对象(即 Referenced 类及其派生类)构建,提供了自己的管理模式,以避免因为用户使用 new运算符创建对象实例之后,没有及时用 delete运算符释放对象而造成内存泄露。

        智能指针的使用为用户提供了一种自动内存释放的机制,即场景图形中的每一个节点均关联一个内存计数器,当计数器的计数减到0时,该对象将被自动释放。用户如果希望释放整个场景图形的节点,则只需要删除根节点,根节点以下的所有分支节点均会被自动删除,不用担心内存泄露的问题。

        要使用 OSG 的智能指针,必须满足以下两个条件:

  • 用户的类必须派生自Referenced类,这样才能使用与其自身关联的内存计数器。
  • 当用户使用智能指针模板 osg::ref_ptr<class T>定义类的实例时,内存计数器即被启用并加1;同理,当ref_ptr 模板超出其生命范围时,类实例的内存计数器将被减1,如果减到0则对象自动被释放。

1.1 Referenced 类

        Referenced类(命名空间:osg)实现了对内存区段的引用计数器功能。所有的0SG节点和场景图形数据,包括状态信息、顶点数组、法线以及纹理坐标,均派生自Referenced类。因此,所有的OSG场景图形均可以进行内存引用计数。

        Referenced 类包括了如下3个主要组成部分:

  • 保护成员整型变量_refCount。用作引用计数,在构造时被初始化为0。
  • 公有函数ref()和unref()。用于实现_refCount值的增加和减少。当_refCount为0时,unref()将自动释放该对象所占用的内存。
  • 作为保护成员存在的虚析构函数。堆栈的创建和显示的析构均会因为析构函数受保护而被禁止,而虚函数的特性将允许用户执行子类的析构函数。总体上来说,用户的代码基本上不需要直接调用ref()和unref()函数,只要使用ref_ptr<>进行处理即可。

1.2 ref_ptr<>模板类

        ref_ptr>(命名空间:osg)用于实现一个指向Referenced对象的智能指针,并对其引用计数器进行管理。当最后一个引用 Referenced 对象的refptr>失去作用时,对象将确保被释放。ref ptr>简化了场景图形内存释放的工作,并保证当错误地调用堆栈展开时,对象也可以被正确释放。

        ref_ptr<>模板类包括以下3个主要组成部分:

  • 一个私有指针_ptr。用于保存管理内存区域的地址,可以用get()方法返回_ptr的值。
  • 为了使ref_ptr<>可以像正常的C++指针一样工作,重载或定义了一些方法,如 operator->()和operator==()。
  • valid()方法。用于判断 ref_ptr<>是否为空,不为NULL时返回tnue。当程序将一个地址指定给ref_ptr<>变量时,ref_ptr<>的重载函数operator==()将会假定此地址指向一个 Referenced 派生对象,并自动调用 Referenced::ref(),将引用计数值自动加1。

        ref_ptr<>变量引用计数值减少的情形有两种,分别是ref_ptr<>被释放(在类的析构函数里执行减1)或重新进行了赋值(在operator==()里执行减1)。在这两种情况中,ref_ptr<>都会通过调用Referenced::unref()来执行减少引用计数值的操作。

1.3 智能指针

        通过上面的介绍,可以看出OSG中的智能指针的机制在管理内存方面是非常智能的。在 OSG中,大多数场景图形类都继承自osg::Reference,因此,在OSG中,智能指针几乎无处不在。

        虽然智能指针能够有效地管理内存,能够在很大程度上避免内存泄露。在实际使用中,还有如下几个需要注意的地方:

  • 使用智能指针模板的类必须继承自osg::Reference 类,否则将无法使用,如osg::ref_ptr<osg::Matrix>的用法是错误的,编译会提示错误。
  • 在创建智能指针之后,不能手动调用 delete 来删除该智能指针,否则编译会提示错误信息。因为 osg::Reference 的析构函数是保护类型的,所以这种行为是不允许发生的。
  • 在 osg::Reference 类成员函数中,有两个共有的成员函数ref()和unref(),它们主要用来控制内存计数器。在应用程序中,不要随意使用ref()和unref()函数来改变内存计数器的值,这样可能会导致对象无故被删除而引起程序崩溃。
  • 在OSG中,同样是允许new运算指针的,如“osg::Node*node=new osg::Node();”这样的做法在OSG是允许的,但是需要注意的是,不能混用智能指针。否则,在读者的应用程序中会出现意想不到的情况。在实际开发中需要统一规定使用某一种方法,这样可以避免很多不必要的错误调试。

2. 访问器机制

2.1 访问器设计模式

        访问器设计模式是对在多个抽象的对象群的一种特殊处理,它可以作为一个作用于某对象结构中的各元素的操作。它使读者可以在不改变各元素的类的前提下定义作用于这些元素的新操作。

        访问器的设计主要适用于以下情况:

  • 一个对象结构包含很多类对象,它们有不同的接口,而读者想对这些对象实施一些依赖于其具体类的操作。
  • 需要对一个对象结构中的对象进行很多不同的并且不相关的操作,而读者想避免让这些操作“污染”这些对象的类。Visitor使读者可以将相关的操作集中起来定义在一个类中。当该对象结构被很多应用共享时,可以用 Visitor 模式让每个应用仅包含需要用到的操作。
  • 定义对象结构的类很少改变,但经常需要在此结构上定义新的操作。改变对象结构类需要重定义对所有访问者的接口,这可能需要很大的代价。如果对象结构类经常改变,那么可能还是在这些类中定义这些操作较好。

2.2 osg::NodeVisitor 类

        osg::NodeVisitor 类是对设计模式 Visitor 中的设计思想的具体实现。osg::NodeVisitor 类继承自osg::Reference 类,继承关系图如图7-1所示。

图7-1 osg:::NodeVisitor 的继承关系图

        从继承关系图可以看出,OSG 众多场景管理类都继承自osg::NodeVisitor 类。可以说 OSG 遍历整个场景函数并调用被访问子节点的函数,如osgUtil::TriStripVisitor等。

        osg::NodeVisitor是一个虚基类,在程序中无法实例化。在osg::NodeVisitor中主要有apply()和accept()两大函数。apply()决定了遍历的方式,如可以获得各个节点的属性,也可以修改节点的属性,这完全取决于apply()函数的实现,用户可以创建新的继承于osg::NodeVisitor的类,重写apply()函数。调用accept()函数可以关联需要访问的节点,并启动访问器进行遍历。在用户应用程序中,可以编写继承自osg::NodeVisitor 的新类,再通过重载 apply()函数来实现自己的功能。

        根据上面访问器的特性,访问器的设计允许应用程序将某个特定节点的指定函数应用到场景中的所有节点。

        遍历的类型包括:

        enum VisitorType

        {

                NODE_VISITOR=0,//节点访问器

                UPDATE_VISITOR,//更新访问器

                EVENT_VISITOR,//时间访问器

                COLLECT_OCCLUDER_VISITOR,//遮挡节点访问器

                CULL_VISITOR //拣选访问器

        };

        遍历的模式包括:

        enum TraversalMModc

        {

                TRAVERSE_NONE.//仅传递到当前节点

                TRAVERSE_PARENTS,//传递给当前节点及父节点

                TRAVERSE_ALL_CHILDREN,//传递给场册中所有的节点及其子节点

                TRAVERSE_ACTTVE_CHILDREN//传递给场景中所有的活动节点及其子节点

        };

        在应用程序中定义自己的访问器主要包括以下步骤:

        (1)继承自osg::NodeVisitor 类写一个新类,重载其中的 apply()方法,添加自己的代码实现相应的功能。

        (2)在应用程序中调用 accept()方法关联相应的节点,启动访问器遍历,下面的内容将着重说明如何定义自己的访问器。

2.3 顶点访问器示例

        顶点访问器(VertexVisitor)示例的代码如程序清单7-1所示,

 // 顶点访问器,继承自osg::NodeVisitor类class VertexVisitor : public osg::NodeVisitor{public://保存顶点数据osg::ref_ptr<osg::Vec3Array> extracted_verts;//构造函数,初始化一下并设置为遍历所有子节点VertexVisitor() :osg::NodeVisitor(osg::NodeVisitor::TRAVERSE_ALL_CHILDREN){extracted_verts = new osg::Vec3Array();}//重载apply方法void apply(osg::Geode& geode){//得到每一个drawablefor (unsigned int i = 0; i < geode.getNumDrawables(); ++i){//得到几何体osg::Geometry* geom = dynamic_cast<osg::Geometry*>(geode.getDrawable(i));if (!geom){std::cout << "几何体错误!" << std::endl;continue;}//得到顶点数组osg::Vec3Array* verts = dynamic_cast<osg::Vec3Array*>(geom->getVertexArray());if (!verts){std::cout << "顶点数组错误!" << std::endl;continue;}//添加到extracted_vertsextracted_verts->insert(extracted_verts->end(), verts->begin(), verts->end());}}};void vertexVisitor_7_1(const string &strDatafolder){// 创建Viewer对象,场景浏览器osg::ref_ptr<osgViewer::Viewer> viewer = new osgViewer::Viewer();osg::ref_ptr<osg::GraphicsContext::Traits> traits = new osg::GraphicsContext::Traits;traits->x = 40;traits->y = 40;traits->width = 600;traits->height = 480;traits->windowDecoration = true;traits->doubleBuffer = true;traits->sharedContext = 0;osg::ref_ptr<osg::GraphicsContext> gc = osg::GraphicsContext::createGraphicsContext(traits.get());osg::ref_ptr<osg::Camera> camera = new osg::Camera;camera->setGraphicsContext(gc.get());camera->setViewport(new osg::Viewport(0, 0, traits->width, traits->height));GLenum buffer = traits->doubleBuffer ? GL_BACK : GL_FRONT;camera->setDrawBuffer(buffer);camera->setReadBuffer(buffer);viewer->addSlave(camera.get());osg::ref_ptr<osg::Group> root = new osg::Group();// 读取滑翔机模型string strNodePath = strDatafolder + "glider.osg";osg::ref_ptr<osg::Node> node = osgDB::readNodeFile(strNodePath);root->addChild(node.get());// 创建顶点访问器对象VertexVisitor vtea;node->accept(vtea); // 开始执行访问器遍历// 申请一个输出流string strNodeOutPath = strDatafolder + "glider.vertexs";std::ofstream fout(strNodeOutPath);// 得到顶点数组的大小int size_t = vtea.extracted_verts->size();// 初始化一个迭代器std::vector <osg::Vec3 > ::iterator iter = vtea.extracted_verts->begin();// 写入文件for (int i = 0; i < size_t; i++){fout << iter->x() << "   " << iter->y() << "   " << iter->z() << std::endl;iter++;}// 优化场景数据osgUtil::Optimizer optimizer;optimizer.optimize(root.get());viewer->setSceneData(root.get());viewer->realize();viewer->run();}

运行程序,截图如图 7-2 所示。

图7-2顶点访问器示例截图

2.4 纹理访问器示例

        纹理访问器(TextureVisitor)示例的代码如程序清单7-2所示。

// 7-2 节点纹理访问器class TextureVisitor : public osg::NodeVisitor{public:// 构造函数,遍历所有子节点TextureVisitor() :osg::NodeVisitor(osg::NodeVisitor::TRAVERSE_ALL_CHILDREN){//}// 重载apply方法virtual void apply(osg::Node& node){if (node.getStateSet()){apply(node.getStateSet());}//实现继续遍历节点traverse(node);}// 重载apply方法virtual void apply(osg::Geode& geode){if (geode.getStateSet()){apply(geode.getStateSet());}unsigned int cnt = geode.getNumDrawables();for (unsigned int i = 0; i < cnt; i++){apply(geode.getDrawable(i)->getStateSet());}traverse(geode);}// 得到贴图列表void apply(osg::StateSet* state){//得到纹理属性列表osg::StateSet::TextureAttributeList& texAttribList = state->getTextureAttributeList();for (unsigned int i = 0; i < texAttribList.size(); i++){//得到纹理osg::Texture2D* tex2D = NULL;if (tex2D = dynamic_cast<osg::Texture2D*>(state->getTextureAttribute(i, osg::StateAttribute::TEXTURE))){//得到贴图if (tex2D->getImage()){//写入映射表_imageList.insert(std::make_pair(tex2D->getImage()->getFileName(), tex2D->getImage()));}}}}// 得到贴图std::map<std::string, osg::Image*>& getImages(void){return _imageList;}protected:// 贴图映射表,用来保存贴图名和贴图std::map<std::string, osg::Image*> _imageList;};// 7-2 节点纹理访问器void textureVisitor_7_2(const string &strDatafolder){// 创建Viewer对象,场景浏览器osg::ref_ptr<osgViewer::Viewer> viewer = new osgViewer::Viewer();osg::ref_ptr<osg::GraphicsContext::Traits> traits = new osg::GraphicsContext::Traits;traits->x = 50;traits->y = 50;traits->width = 1000;traits->height = 800;traits->windowDecoration = true;traits->doubleBuffer = true;traits->sharedContext = 0;osg::ref_ptr<osg::GraphicsContext> gc = osg::GraphicsContext::createGraphicsContext(traits.get());osg::ref_ptr<osg::Camera> camera = new osg::Camera;camera->setGraphicsContext(gc.get());camera->setViewport(new osg::Viewport(0, 0, traits->width, traits->height));GLenum buffer = traits->doubleBuffer ? GL_BACK : GL_FRONT;camera->setDrawBuffer(buffer);camera->setReadBuffer(buffer);viewer->addSlave(camera.get());osg::ref_ptr<osg::Group> root = new osg::Group();// 创建一个节点并读取牛的模型string strNodePath = strDatafolder + "cow.osg";osg::ref_ptr<osg::Node> node = osgDB::readNodeFile(strNodePath);// 创建纹理访问器TextureVisitor textureTV;node->accept(textureTV); // 启动访问器,执行遍历std::map<std::string, osg::Image*> imageList = textureTV.getImages(); // 初始化一个贴图映射表std::map<std::string, osg::Image*>::iterator iter = imageList.begin(); // 初始化一个映射表迭代器unsigned int cnt = 0;char strTemp[256];for (; iter != imageList.end(); ++iter){sprintf(strTemp, "TextureImage%d.jpg", ++cnt);osgDB::writeImageFile(*(iter->second), strTemp);// 写入文件}root->addChild(node.get());// 优化场景数据osgUtil::Optimizer optimizer;optimizer.optimize(root.get());viewer->setSceneData(root.get());viewer->realize();viewer->run();}

        运行程序,截图如图 7-3 所示

图7-3 纹理访问器示例截图

2.5 节点访问器示例

        节点访问器(FindNodeVisitor)示例的代码如程序清单7-3所示。

#ifndef FIND_NODE_VISITOR_H#define FIND_NODE_VISITOR_H#include <osg/NodeVisitor>#include <osg/Node>#include <osgSim/DOFTransform>#include <iostream>#include <vector>#include <string>// 节点查找访问器,继承自osg::NodeVisitorclass FindNodeVisitor : public osg::NodeVisitor{public:// 构造函数,参数为需要查找的节点名FindNodeVisitor(const std::string &searchName) ;// 重载apply方法virtual void apply(osg::Node &searchNode);virtual void apply(osg::Geode &geode);virtual void apply(osg::Transform &searchNode);// 设置要查找的节点名void setNameToFind(const std::string &searchName);// 得到查找节点向量的第一个节点osg::Node* getFirst();                                                                              // 定义一个节点向量typedef std::vector<osg::Node*> nodeListType;// 得到查找节点向量nodeListType& getNodeList(){return foundNodeList;}private:std::string searchForName;  // 节点名 nodeListType foundNodeList; // 用来保存查找的节点};#endif#include "FindNodeVisitor.h"//构造函数,初始化并设置遍历所有的子节点FindNodeVisitor::FindNodeVisitor(const std::string &searchName) :osg::NodeVisitor(TRAVERSE_ALL_CHILDREN),searchForName(searchName){//}//重载apply方法                                 void FindNodeVisitor::apply(osg::Node &searchNode){//判断节点名称是否与查找的节点名称一样if (searchNode.getName() == searchForName){//添加到节点系列foundNodeList.push_back(&searchNode);}//继续遍历traverse(searchNode);}//重载apply方法void FindNodeVisitor::apply(osg::Transform &searchNode){apply ((osg::Node&)searchNode);traverse(searchNode);}//重载apply方法void FindNodeVisitor::apply(osg::Geode &geode){apply ( (osg::Node&)geode);traverse((osg::Node&)geode);}//设置要查找的节点名称void FindNodeVisitor::setNameToFind(const std::string &searchName){searchForName = searchName;foundNodeList.clear();}//得到查找节点向量中第一个节点osg::Node* FindNodeVisitor::getFirst(){return *(foundNodeList.begin());}#include <osgViewer/Viewer>#include <osg/Node>#include <osg/Geode>#include <osg/Group>#include <osgDB/ReadFile>#include <osgDB/WriteFile>#include <osgUtil/Optimizer>#include "FindNodeVisitor.h"int main(){// 创建Viewer对象,场景浏览器osg::ref_ptr<osgViewer::Viewer> viewer = new osgViewer::Viewer();osg::ref_ptr<osg::Group> root = new osg::Group();// 创建一个节点,读取牛的模型osg::ref_ptr<osg::Node> node = osgDB::readNodeFile("cow.osg");// 创建节点查找访问器FindNodeVisitor cow("cow.osg");node->accept(cow); //启动访问器,开始执行遍历if(!cow.getFirst()){std::cout<<"无法找到节点,查找失败"<<std::endl ;}else{std::cout<<"查找节点成功,成功找到节点"<<std::endl ;}root->addChild(node.get());// 优化场景数据osgUtil::Optimizer optimizer ;optimizer.optimize(root.get()) ;viewer->setSceneData(root.get());viewer->realize();viewer->run();return 0 ;}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/549452.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

力扣106---从中序和后序序列构造二叉树

题目描述&#xff1a; 给定两个整数数组 inorder 和 postorder &#xff0c;其中 inorder 是二叉树的中序遍历&#xff0c; postorder 是同一棵树的后序遍历&#xff0c;请你构造并返回这颗 二叉树 。 示例 1: 输入&#xff1a;inorder [9,3,15,20,7], postorder [9,15,7,20…

深度学习神经网络相关记录《二》

如何判断模型是一个好模型&#xff1f; 模型预测效果&#xff0c;也就是模型预测的准确率运算速度&#xff1b;能够处理大量数据、短时间内急速学习、可以实时进行预测&#xff0c;是机器学习的重要优势&#xff1b;可解释性&#xff1b;深度学习已经不太关系这一点了&#xf…

zookeeper集群安装部署和集群异常处理

准备jdk和zookeeper安装包【官网即可下载】 zookeeper-3.5.1-alpha.tar.gz jdk1.7.0_8020200612.tar 准备三台linux虚拟机【具体以项目实际需要为准】&#xff0c;并安装jdk和zookeeper 虚拟机地址如下&#xff1a;194.1.1.86&#xff08;server.1&#xff09;、194.1.1.74…

华为配置WAPI-PSK安全策略实验

配置WAPI-PSK安全策略示例 组网图形 图1 配置WAPI-PSK安全策略组网图 配置流程组网需求配置思路配置注意事项操作步骤配置文件 配置流程 WLAN不同的特性和功能需要在不同类型的模板下进行配置和维护&#xff0c;这些模板统称为WLAN模板&#xff0c;如域管理模板、射频模板、VAP…

服务器相关知识点总结

一、服务器概述 1.服务器的定义 服务器是计算机的一种&#xff0c;是网络中为客户端计算机提供各种服务的高性能的计算机。服务器在网络操作系统的控制下&#xff0c;将与其连接的硬盘、磁带、打印机以及昂贵的专用通讯设备提供给网络上的客户站点共享&#xff0c;也能为网络用…

无人机三维建模过程中注意事项

无人机三维建模是指利用无人机技术进行三维建模&#xff0c;该方法通过无人机搭载的多种传感器&#xff0c;如摄像头、激光扫描仪等&#xff0c;获取建筑物的多角度影像数据&#xff0c;然后利用计算机视觉技术和三维重建算法&#xff0c;将这些影像数据转化为高精度的三维模型…

机器人在果园内行巡检仿真

文章目录 创建工作空间仿真果园场景搭建小车模型搭建将机器人放在仿真世界中创建工作空间 mkdir -p ~/catkin_ws/src cd ~/catkin_ws仿真果园场景搭建 cd ~/catkin_ws/src git clone https://gitcode.com/clearpathrobotics/cpr_gazebo.git小车模型搭建 DiffBot是一种具有两个…

robots协议详解:爬虫也要有边界感

随着互联网的迅猛发展,信息的获取变得越来越便捷,而网络爬虫(Spider)技术就是其中之一。网络爬虫是一种自动化程序,它能够遍历互联网上的网页,提取信息,用于各种用途,例如搜索引擎索引、数据挖掘、价格比较等。但是,爬虫技术虽然强大,但是也是一把双刃剑,在正当使用…

视频桥接芯片#LT8912B适用于MIPIDSI转HDMI+LVDS应用方案,提供技术支持。

1. 概述 Lontium LT8912B MIPI DSI 转 LVDS 和 HDMI 桥接器采用单通道 MIPI D-PHY 接收器前端配置&#xff0c;每通道 4 个数据通道&#xff0c;每个数据通道以 1.5Gbps 的速度运行&#xff0c;最大输入带宽高达 6Gbps。 对于屏幕应用&#xff0c;该桥接器可解码 MIPI DSI 18bp…

【Web】浅聊Hessian反序列化之打Spring AOP——JNDI

目录 前言 简单分析 EXP 前言 前文&#xff1a;【Web】浅聊Java反序列化之Rome——关于其他利用链-CSDN博客 前文里最后给到一条HotSwappableTargetSource利用链&#xff0c;就是我们今天PartiallyComparableAdvisorHolder链子的前半段(触发恶意类的toString方法)&#xf…

计算机网络:TCP篇

计网tcp部分面试总结 tcp报文格式&#xff1a; 序列号&#xff1a;通过SYN传给接收端&#xff0c;当SYN为1&#xff0c;表示请求建立连接&#xff0c;且设置序列号初值&#xff0c;后面没法送一次数据&#xff0c;就累加数据大小&#xff0c;保证包有序。 确认应答号&#x…

【神经网络 基本知识整理】(激活函数) (梯度+梯度下降+梯度消失+梯度爆炸)

神经网络 基本知识整理 激活函数sigmoidtanhsoftmaxRelu 梯度梯度的物理含义梯度下降梯度消失and梯度爆炸 激活函数 我们知道神经网络中前一层与后面一层的连接可以用y wx b表示&#xff0c;这其实就是一个线性表达&#xff0c;即便模型有无数的隐藏层&#xff0c;简化后依旧…