<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)

    {

        //得到每一个drawable

        for (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_verts

            extracted_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::NodeVisitor

class 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 ;

}

  • 52
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
### 回答1: 《Visual C++权威剖析--MFC的原理、机制与开发实例》是一本介绍MFC(Microsoft Foundation Classes)的原理、机制以及开发实例的权威性书籍。MFC是微软公司用于开发Windows应用程序的一套类库,是在C++基础上实现的,为开发者提供了一些常用的类和函数,简化了Windows应用程序的开发过程。 这本书首先对MFC的基本原理进行了详细的介绍。它解释了MFC的框架结构,包括应用程序对象、窗口类和消息映射等重要组成部分。同时,它还深入讲解了MFC的事件处理机制、资源管理和界面设计等方面的内容,帮助读者全面理解MFC的内部机制。 除了原理的介绍,这本书还给出了一些实际的开发实例。通过这些实例,读者可以学习如何使用MFC来创建一个完整的Windows应用程序。这些实例包括了常见的功能,例如窗口创建、菜单设计、对话框布局以及文件操作等。通过实际的案例,读者可以加深对MFC的理解,并且在实践中提高自己的开发能力。 总体来说,《Visual C++权威剖析--MFC的原理、机制与开发实例》是一本权威且实用的书籍,适合有一定C++基础并且对Windows应用程序开发感兴趣的读者。通过学习这本书,读者可以更好地掌握MFC的原理和使用技巧,提高自己的软件开发能力。 ### 回答2: 《Visual C++权威剖析--MFC的原理、机制与开发实例》是一本介绍MFC(Microsoft Foundation Classes)的书籍。MFC 是微软公司推出的一个非常重要的C++框架,用于在Windows操作系统上开发图形用户界面应用程序。 这本书首先解释了MFC的基本原理和机制。MFC建立在Windows API之上,它提供了一套面向对象的类库,用于简化Windows GUI应用程序的开发。MFC封装了很多常用的控件和功能,并且提供了很多易于使用的类和函数来处理用户输入、显示输出、消息处理、资源管理等。 书中还介绍了如何使用MFC开发实例。通过这些实例,读者可以学习如何创建一个基本的MFC应用程序,并了解如何利用界面设计器来设计用户界面。书中还提供了一些示例代码,展示了如何处理按钮点击事件、菜单点击事件、对话框等。通过这些实例,读者可以学习到MFC的具体用法和开发技巧。 此外,书中还深入探讨了MFC的高级特性和扩展机制。例如,它介绍了如何使用ActiveX 控件、数据库编程、多线程编程等。这些内容可以帮助读者更加深入地了解MFC并提高开发能力。 总的来说,《Visual C++权威剖析--MFC的原理、机制与开发实例》是一本详细介绍MFC的权威书籍。它适合有一定C++基础,想要学习MFC开发的读者。通过阅读这本书,读者可以系统地学习MFC的原理、机制和开发实例,从而提高Windows GUI应用程序的开发能力。 ### 回答3: 《Visual C++权威剖析--MFC的原理、机制与开发实例》是一本面向Visual C++开发者的权威指南,全面介绍MFC(Microsoft Foundation Class)的原理、机制和开发实例。MFC是微软提供的一组C++类库,用于开发Windows应用程序。 该书首先介绍了MFC的发展历史以及其与Windows操作系统的关系。接着详细解析了MFC的体系结构和设计原理,包括C++面向对象编程、事件和消息处理、窗口和控件等基本概念。通过深入理解MFC的原理和机制,读者可以更加高效地利用MFC进行应用程序开发。 本书还提供了大量的开发实例,涵盖了MFC的各个方面,包括界面设计、数据处理、线程管理等。每个实例都提供了详细的代码和演示步骤,读者可以根据实例来学习并实践MFC的开发技术。通过实例的学习,读者可以了解MFC在实际开发中的应用场景和解决问题的方法。 此外,该书还介绍了一些高级的MFC主题,如自定义控件、打印和预览、国际化等。这些主题涉及到一些常见但较为复杂的开发问题,通过学习这些内容可以进一步提升开发者的技术水平。 总之,《Visual C++权威剖析--MFC的原理、机制与开发实例》是一本全面系统的MFC教程,适合有一定C++编程基础的开发者阅读。通过学习这本书,读者可以深入了解并熟练掌握MFC的原理、机制和开发技术,提高自己在Windows应用程序开发中的能力。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

听风者868

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值