OSG加载模型时显示读取进度

目录

1. 前言

2. 开发环境说明

3. 功能实现

3.1. 方法1

3.2. 方法2

3.3. 方法3

4. 附加说明


1. 前言

       OSG中加载模型文件到视景器,一般通过osgDB::readXXXX系列开头的函数来加载模型,如:osgDB::readNodeFile、osgDB::readImageFile、osgDB::readObjectFile等系列函数。如果想深入探究read开头的这些函数内部的实现机制,请参考:osgDB::readNodeFile等函数源码剖析 博文。如果能深入理解read开头的这些函数,理解本博文就很容易了。

      OSG中加载模型文件到视景器的典型代码片段类似如下:

int main()
{
    auto pCowNode = osgDB::readNodeFile("cow.osg");
    if(nullptr == pCowNode)
    {
        OSG_WARN << "cow node is nullptr!" << std::endl; // 注意加换行符,否则控制台不会有输出
        return 1;
    }

    sg::ref_ptr<osgViewer::Viewer> pViewer = new osgViewer::Viewer;
    pViewer ->setSceneData(pCowNode );
    osgDB::readNodeFile

    return viewer->run();
}

上面的代码,不会显示进度,当模型文件很大时,只能干等着加载完,期间,也不知道到底加载多少了,即当前时刻到底加载了百分之几呢?如何实现告知用户加载的百分比进度?

2. 开发环境说明

        本次用到的开发环境如下:

  • OpenSceneGraph 3.6.2。
  • Visual studio 2022 64位社区版。
  • Windows 11 家庭中文版。

3. 功能实现

       本博文以读取osg文件来讲解,通过三种方法来说明如何实现读取文件进度。

3.1. 方法1

       本方法直接通过操作读取指定后缀名的文件读写器类实现读取进度。OSG读取模型文件在内部流程是按下述步骤进行:

  1. 获取该文件的后缀名。
  2. osg内部维护了一个后缀名和读写器类对象构成的map。根据步骤1中的后缀名从map中找到读写器类对象。
  3. 利用步骤2中的读写器类对象调用read开头的函数读取模型。

用于读取后缀名为osg的文件的读写器类位于osg源码的如下子目录:

src/osgPlugins/osg/ReaderWriterOSG.cpp

 用于读取后缀名为osg的文件的读写器类以插件形式位于osg解决方案下的如下工程:

该工程下ReaderWriterOSG.cpp文件的OSGReaderWriter就是用来读写后缀名为osg的读写器类,如下:

          OSGReaderWriter最终是通过调用如下的readNode函数实现读取osg文件的:

        virtual ReadResult readNode(std::istream& fin, const Options* options) const
        {
            loadWrappers();

            fin.imbue(std::locale::classic());

            Input fr;
            fr.attach(&fin);
            fr.setOptions(options);
            ...... // 其它代码略
       }

这样,就可以直接构造一个OSGReaderWriter类对象,通过传入流对象读取osg文件,同时将该流对象传入一个线程,在线程中实时获取当前流的文件指针的位置,从而获取到读取进度,代码实现如下:

#include <osgViewer/Viewer>
#include <osgDB/ReaderWriter>
#include <osgDB/ReadFile>
#include <osgDB/FileNameUtils>
#include <osgDB/FileUtils>
//#include <osgDB/Registry>
#include <fstream>
#include<thread>


/* 此处加入读取指定后缀名文件的读写器文件或头文件。本例读取的是osg文件,对应的读写器类为OSGReaderWriter
   最好是换成你本机的相对路径
*/
#include"E:/osg/OpenSceneGraph-OpenSceneGraph-3.6.2/src/osgPlugins/osg/ReaderWriterOSG.cpp"

// 线程函数,在该函数中不停地检查文件指针的位置
void readFileProgressFun(std::ifstream& im)
{
    im.seekg(0, std::ifstream::end);
    auto fileSize = im.tellg(); // 获取模型文件的总长度

    im.seekg(0, std::ifstream::beg); // 定位到模型文件的开头

    auto bEnd = false;
    while (!bEnd)
    {
        auto curPos = im.tellg(); // 获取文件指针当前位置
        if (-1 == curPos) // 到达文件尾部了,即文件全部读取完
        {
            /* 因为每读取一次文件,文件指针就前进一段距离(如:blocksize),除了最后一次读取的外,其它
            *  每次文件指针前进的距离应该是一样的。但最后一次读取的字节数可能不够blocksize,此时im.tellg()就会返回-1,表示文件指针已经到尾部了,此时就将当前位置设为文件大小
            */
            curPos = fileSize; 
            bEnd = true;
        }

        std::cout << curPos * 100.0 / fileSize <<"%" << std::endl;
    }
}

int main()
{
    
    std::string fileName = osgDB::findDataFile("cow.osg", nullptr);
    if (!osgDB::fileExists(fileName))
    {
        OSG_WARN << "cow.osg is not exist!" << std::endl; // 加换行符,否则控制台不会显示打印信息,下同
        return osgDB::ReaderWriter::ReadResult::FILE_NOT_FOUND;
    }

    //osgDB::ifstream istream(R"(cow.osg)", std::ios::in | std::ios::binary);
    osgDB::ifstream istream(fileName.c_str(), std::ios::in | std::ios::binary);
    auto bIsGood = istream.good();
    if (!bIsGood) // 检查读取流是否打开成功 
    {
        OSG_WARN << "istream is failed" << std::endl; 
        return 1;
    }

    OSGReaderWriter rw; // 定义一个用于读取osg的文件读写器类对象

    // 开启一个线程,在此线程中,显示读取进度
    std::thread readFileProgressThread(readFileProgressFun, std::ref(istream));
    readFileProgressThread.detach();
    auto pCowNode = rw.readNode(istream, nullptr).takeNode();
    if (nullptr == pCowNode)
    {
        OSG_WARN << "cow node is null" << std::endl;  
        return 1;
    }

    osg::ref_ptr<osgViewer::Viewer> pViewer = new osgViewer::Viewer;
    pViewer->setSceneData(pCowNode);
 
    return pViewer->run();
}

说明:

       上面代码的思路是:直接构造一个读取osg文件的读写器类对象(第60行),然后通过文件名构造一个输入文件流对象,然后将该流对象作为第1个参数传入读写器类对象的readNode函数,且将该流对象传入线程函数,当线程执行时,在一个循环中检查流的文件指针位置,并打印出读取百分比,当文件指针到达文件尾部时,线程循环跳出,文件读取完。

      获取osg文件的路径时,请按照第44行那样调用osgDB::findDataFile函数获取,该函数先从osgDB::Options查找指定的文件,并返回文件在本机文件系统中的绝对路径;如果找不到,再从当前工作目录查找,并返回文件的绝对路径;如果找不到,再从本机环境变量OSG_FILE_PATH中设置的目录查找,并返回文件在本机文件系统中的绝对路径,如果上面的都找不到,则返回空字符串。如果你想像第51行那样,则请写入绝对路径或相对路径,保证能找到该文件。

      上述代码运行结果如下:

3.2. 方法2

       方法1是直接构造一个读取指定后缀名文件的读写器类对象,然后再读取文件的,这要求开发人员对osg的源码很熟。有时我们并不知道指定后缀名的读写器类是哪个类,且每次文件的类型即后缀名变了,如:方法1是读取后缀名为.osg的文件,下次碰到后缀名为.ive的文件时,得改方法1的第60行代码。如果能够根据传入的模型文件的后缀名,让程序为我们自动选择读写器类,那就省事多了。接下来是方法2实现的步骤:

1. 先写个读取文件的callback,让readNodeFile的时候调一下我们的callback.

class ReadFileCB : public osgDB::Registry::ReadFileCallback

然后安装这个回调,它是这样被安装调用的:

osgDB::Registry::instance()->setReadFileCallback(new ReadFileCB);

2. 在callback中手动获取读写的插件,然后使用如下代码:

osgDB::ifstream istream(fileName.c_str(), std::ios::in | std::ios::binary);

读取文件,使用如下代码:

rw->readNode(istream);

来读取结点。

3. 起一个线程,实时的获取istream在整个文件中的位置,来求百分比。下面是线程类,先获取了总长度_length,然后在run里看看当前位置,当读到最后的时候线程退出。

整个代码如下: 

#include <osgViewer/Viewer>
#include <osgDB/ReaderWriter>
#include <osgDB/ReadFile>
#include <osgDB/FileNameUtils>
#include <osgDB/FileUtils>
#include <osgDB/Registry>
#include <fstream>


class CRenderingThread : public OpenThreads::Thread
{
public:
    CRenderingThread(osgDB::ifstream* fin) :_fin(fin)
    {
        fin->seekg(0, std::ifstream::end);
        _length = fin->tellg();
        fin->seekg(0, std::ifstream::beg);

    };
    virtual ~CRenderingThread() {};

    virtual void run()
    {
        int pos = _fin->tellg();
        while (pos < _length)
        {
            pos = _fin->tellg();
            if(-1 == pos)
            {
               pos = _length;
            }
            std::cout << 100.0*pos / _length<<"%" << std::endl;
        }
    };

protected:
    osgDB::ifstream* _fin;
    int _length;
};


class ReadFileCB : public osgDB::Registry::ReadFileCallback
{
public:

    virtual osgDB::ReaderWriter::ReadResult readNode(const std::string& file, const osgDB::ReaderWriter::Options* opt)
    {
        //第一步是获取OSG、IVE ReaderWriter
        std::string ext = osgDB::getLowerCaseFileExtension(file);
        osgDB::ReaderWriter* rw = osgDB::Registry::instance()->getReaderWriterForExtension(ext);

        if (!rw)
        {
            return osgDB::ReaderWriter::ReadResult::FILE_NOT_HANDLED;
        }

        std::string fileName = osgDB::findDataFile(file, opt);
        if (fileName.empty()) return osgDB::ReaderWriter::ReadResult::FILE_NOT_FOUND;

        //osgDB::ifstream fin(fileName.c_str());
        osgDB::ifstream istream(fileName.c_str(), std::ios::in | std::ios::binary);
        CRenderingThread crt(&istream);
        crt.startThread();
        auto bIsGood = istream.good();
        if (bIsGood)
        {
            std::cout << "Using default format readerwriter" << std::endl;

            osgDB::ReaderWriter::ReadResult rr = rw->readNode(istream);
            while (crt.isRunning()) {}
            return rr;
        }
        else
        {
            return osgDB::ReaderWriter::ReadResult::ERROR_IN_READING_FILE;
        }
    }
};

int main()
{
    osg::ref_ptr<osgViewer::Viewer> viewer = new osgViewer::Viewer;
    osgDB::Registry::instance()->setReadFileCallback(new ReadFileCB);
    viewer->setSceneData(osgDB::readNodeFile("ceep.ive"));
    return viewer->run();
}

注意:

  • 方法1和方法2开启线程采取的不同方法,前者采用std::thread类,后者采用开源线程库  OpenThreads::Thread。
  • 如果读取的是后缀为.osg的文件,则第52行返回的rw就是OSGReaderWriter类对象。
  • 只要实现了某种后缀名文件的读写器类,并将其以插件的形式挂载到osg内核了,本方法就可可以通过第50行代码获取到该读写器类对象。

3.3. 方法3

      下面的代码使用文件回调ReadFileCallback实现了文件读取进度的控制。其基本思想为:重构标准模板库std::basic_filebuf的uflow函数,在其中记录当前读入的字节数;然后使用std::istream类加载文件流,并直接使用readNode函数流的方式来读取文件。但本方法不一定适用于osg支持的所有文件格式,因为有的格式可能不支持数据流方式读取。

#include <osgViewer/Viewer>
#include <osgDB/ReaderWriter>
#include <osgDB/ReadFile>
#include <osgDB/FileNameUtils>
#include <osgDB/FileUtils>
#include <osgDB/Registry>
#include <iostream>
 
template<typename Elem, typename Tr = std::char_traits<Elem>>
class ProgressingStreamBuf : public std::basic_filebuf<Elem, Tr>
{
public:
    typedef std::basic_filebuf<Elem, Tr> BaseType;
    ProgressingStreamBuf(const std::string& filename)
        :BaseType(), _count(0), _readSize(0)
    {
        if (open(filename.c_str(), std::ios_base::in | std::ios_base::binary))
        {
            pubseekoff(0, std::ios_base::beg, std::ios_base::in);
 
        }
 
    }
protected:
 
    virtual int_type uflow()
    {
        int_type value = BaseType::uflow();
        _count++;
        _readSize += egptr() - gptr();
 
        if (0 == (_count % 10))
        {
            std::cout << _readSize;
        }
        else
        {
            std::cout << ".";
        }
 
        return value;
     }
 
    int _count;
    int _readSize;
};
 
 
class  ProgressingReadCallback : public osgDB::Registry::ReadFileCallback
{
public:
    typedef ProgressingStreamBuf<char> ProgressingStringBuf;
    typedef osgDB::ReaderWriter::ReadResult ReadResult;
    ProgressingReadCallback() {}
 
    virtual osgDB::ReaderWriter::ReadResult readNode(const std::string& file, const osgDB::Options* option)
    {
        std::string ext = osgDB::getLowerCaseFileExtension(file);
        osgDB::ReaderWriter::ReadResult rr;
        osgDB::ReaderWriter* rw = osgDB::Registry::instance()->getReaderWriterForExtension(ext);
 
        if (rw)
        {
            std::string fileName = osgDB::findDataFile(file, option);
            if (fileName.empty()) return ReadResult::ReadResult::FILE_NOT_FOUND;
 
            ProgressingStringBuf* ps = new ProgressingStringBuf(fileName);
            if (!ps->is_open())
            {
                return ReadResult::ERROR_IN_READING_FILE;
            }
 
            std::istream s(ps);
            rr = rw->readNode(s, option);
            delete ps;
        }
        else
        {
            rr = osgDB::Registry::instance()->readNodeImplementation(file, option);
        }
 
        return rr;
    }
};
 
int main(int argc, char**argv)
{
    osg::ArgumentParser arguments(&argc, argv);
    osgDB::Registry::instance()->setReadFileCallback(new ProgressingReadCallback);
    
    osg::Node* model = osgDB::readNodeFiles(arguments);
    if (!model) model = osgDB::readNodeFile("cow.osg");
 
    osgViewer::Viewer viewer;
    viewer.setSceneData(model);
    return viewer.run();
}

运行结果如下:

关于std::basic_filebuf的用法,请参考:std::basic_filebuf 

4. 附加说明

       不论是哪种方法,指定后缀名的文件读写器类插件应存在,否则对于第1种方法无法构造读写器类对象,即第1种方法的第60行没法构造出一个具体的读写器对象;对于第2种方法的第52行的if语句将会为true,从而导致返回

osgDB::ReaderWriter::ReadResult::FILE_NOT_HANDLED

;对于第3种方法,则会执行第79行的else语句。

    现有osg读写插件,如果不支持特定的文件类型,就得自己写个读写器的插件,并将该插件挂载到osg内核上。关于怎么实现自定义的读写器插件,请参考:osg实现自定义插件读取自定义格式的模型文件到场景

       方法2转载自第19节 实例-显示模型读取进度

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Pytest自动化测试框架:mark用法---测试用例分组执行

pytest中的mark&#xff1a; mark主要用于在测试用例/测试类中给用例打标记(只能使用已注册的标记名)&#xff0c;实现测试分组功能&#xff0c;并能和其它插件配合设置测试方法执行顺序等。 如下图&#xff0c;现在需要只执行红色部分的测试方法&#xff0c;其它方法不执行&am…

【精选】项目管理工具——Maven详解

Maven简介 Maven是一个项目管理工具。它可以帮助程序员构建工程&#xff0c;管理jar包&#xff0c;编译代码&#xff0c;完成测试&#xff0c;项目打包等等。 Maven工具是基于POM&#xff08;Project Object Model&#xff0c;项目对象模型&#xff09;实现的。在Maven的管理下…

【React】React 基础

1. 搭建环境 npx create-react-app react-basic-demo2. 基本使用 JSX 中使用 {} 识别 JavaScript 中的表达式&#xff0c;比如变量、函数调用、方法调用等。 if、switch、变量声明等属于语句&#xff0c;不是表达式。 列表渲染使用 map 。 事件绑定用&#xff1b;on 事件名称…

公寓水电管理系统

springbootmybatisthymeleaf 这次练习是尝试将layer与系统结合起来&#xff0c;将新增、修改、删除都和弹窗结合起来。 一、需求分析 二、数据库 三、模块 1、登录页面 哈哈哈&#xff0c;之前做的登录页面都好丑&#xff0c;这是目前做的最好看的一次了。 超级管理员&…

Java 教育局民办教育信息服务与监管平台

1) 项目背景 按照《中华人民共和国民办教育促进法》和《中华人民共和国政府信息公开条例》的相关规定&#xff0c;为满足学生和家长、社会各界获取权威信息的需求&#xff0c;着力解决服务老百姓最后一公里问题&#xff0c;达到宣传民办教育和引导家长择校的效果&#xff0…

Java实现图书管理系统

今天与大家分享的是一个图书管理系统&#xff0c;这里我们运用的是java基础的语法其中包括类和对象、继承、封装、多态、抽象类、接口还有数组等。 我们需要实现一个可以进行管理员操作和用户操作的图书管理系统&#xff0c;其中包括了管理员操作(查找&#xff0c;添加&#x…

SpringBoot中日志的使用log4j

SpringBoot中日志的使用log4j 项目中日志系统是必不可少的&#xff0c;目前比较流行的日志框架有 log4j、logback 等&#xff0c;这两个框架的作者是同一个 人&#xff0c;Logback 旨在作为流行的 log4j 项目的后续版本&#xff0c;从而恢复 log4j 离开的位置。 另外 slf4j(…

定点整数、小数

文章目录 一、定点整数二、定点小数三、定点小数的加/减运算 一、定点整数 二、定点小数 三、定点小数的加/减运算 对两个定点小数A、B进行加法/减法时&#xff0c;需要先转换为补码 计算机硬件如何做定点小数补码的加法&#xff1a;从最低位开始&#xff0c;按位相加&#x…

栈与队列:设计循环队列

目录 题目&#x1f525;&#xff1a; 数据模型&#xff1a; 本题大意&#xff1a; 思路分析&#xff1a; 代码分析&#xff1a; 一、定义队列 二、初始化、判断队列的空和满⭐ 初始化&#xff1a; 空满的判断&#xff1a; 三、入队和出队&#x1f387; 入队&…

Vue中实现div的任意移动

前言 在系统应用中&#xff0c;像图片&#xff0c;流程预览及打印预览等情况&#xff0c;当前视窗无法全部显示要预览的全部内容&#xff0c;设置左右和上下滚动条后&#xff0c;如果用鼠标拖动滚动条&#xff0c;又不太便利&#xff0c;如何用鼠标随意的移动呢&#xff1f; …

前端面试:如何实现并发请求数量控制?

题目&#xff1a;实现一个并发请求函数concurrencyRequest(urls, maxNum) 要求如下&#xff1a; 要求最大并发数 maxNum;每当有一个请求返回&#xff0c;就留下一个空位&#xff0c;可以增加新的请求;所有请求完成后&#xff0c;结果按照 urls 里面的顺序依次打出&#xff1b;…

.babyk勒索病毒解析:恶意更新如何威胁您的数据安全

导言&#xff1a; 在数字时代&#xff0c;威胁不断进化&#xff0c;其中之一就是.babyk勒索病毒。这种病毒采用高级加密算法&#xff0c;将用户文件锁定&#xff0c;并要求支付赎金以获取解密密钥。本文91数据恢复将深入介绍.babyk勒索病毒的特点、如何应对被加密的数据&#…

【Promise12数据集】Promise12数据集介绍和预处理

【Segment Anything Model】做分割的专栏链接&#xff0c;欢迎来学习。 【博主微信】cvxiayixiao 本专栏为公开数据集的介绍和预处理&#xff0c;持续更新中。 要是只想把Promise12数据集的raw形式分割为png形式&#xff0c;快速导航&#xff0c;直接看2&#xff0c;4标题即可 …

arcgis属性表十进制度转换成度分秒格式--转换坐标注记法

1、有一组点数据&#xff0c;如下&#xff1a; 2、为其添加XY坐标&#xff0c;如下&#xff1a; 打开属性表&#xff0c;可得到对应点的XY的十进制度坐标&#xff0c;如下&#xff1a; 3、将十进制度转换成度分秒格式&#xff0c;如下&#xff0c;使用转换坐标注记法工具&#…

FPGA实现平衡小车(文末开源!!)

FPGA平衡小车 一. 硬件介绍 底板资源: TB6612电机驱动芯片 * 2 MPU6050陀螺仪 WS2812 RGB彩色灯 * 4 红外接收头 ESP-01S WIFI 核心板 微相 A7_Lite Artix-7 FPGA开发板 电机采用的是平衡小车之家的MG310(GMR编码器)电机。底板上有两个TB6612芯片&#xff0c;可以驱动…

云原生微服务-理论篇

文章目录 分布式应用的需求分布式架构治理模式演进ESB 是什么&#xff1f;微服务架构 MSA微服务实践细节微服务治理框架sidercar 什么是service mesh&#xff1f;康威定律微服务的扩展性什么是MSA 架构&#xff1f;中台战略和微服务微服务总体架构组件微服务网关服务发现与路由…

【GUI】-- 10 贪吃蛇小游戏之静态面板绘制

GUI编程 04 贪吃蛇小游戏 4.1 第一步&#xff1a;先绘制一个静态的面板 首先&#xff0c;需要新建两个类&#xff0c;一个StartGame类作为游戏的主启动类&#xff1b;一个GamePanel类作为游戏的面板类。此外&#xff0c;再新建一个Data类作为数据中心(存放了小蛇各部分图像的…

Halcon (5):Halcon Solution Guide I basics 导论解析

文章目录 文章专栏前言文章目录翻译文档的说明 结论LOL比赛结局 文章专栏 Halcon开发 前言 今天开始看Halcon的官方文档。由于市面上的教学主要是以基础的语法&#xff0c;算子简单介绍为主。所以我还是得看官方的文本。别的不多说了。有道词英语词典&#xff0c;启动。 还有…

LeetCode【36】有效的数独

题目&#xff1a; 思路&#xff1a; https://blog.51cto.com/u_15072778/3788083 代码&#xff1a; public boolean isValidSudoku(char[][] board) {// 二维数组第一个标识 0-9行&#xff0c;第二个表示 0-9数字&#xff0c;存的内容boolean 表示第0-9行&#xff0c;0-9这些…

react之基于@reduxjs/toolkit使用react-redux

react之基于reduxjs/toolkit使用react-redux 一、配置基础环境二、使用React Toolkit 创建 counterStore三、为React注入store四、React组件使用store中的数据五、实现效果六、提交action传递参数七、异步状态操作 一、配置基础环境 1.使用cra快速创建一个react项目 npx crea…
最新文章