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::readNodeFilereturn 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 ReaderWriterstd::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.hqwc.cn/news/194580.html

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

相关文章

【深度学习实验】注意力机制(二):掩码Softmax 操作

文章目录 一、实验介绍二、实验环境1. 配置虚拟环境2. 库版本介绍 三、实验内容0. 理论介绍a. 认知神经学中的注意力b. 注意力机制&#xff1a; 1. 注意力权重矩阵可视化&#xff08;矩阵热图&#xff09;2. 掩码Softmax 操作a. 导入必要的库b. masked_softmaxc. 实验结果 ​ …

05_SHELL编程之文本处理工具SED

typora-root-url: pictures课程目标 掌握sed的基本语法结构 熟悉sed常用的命令&#xff0c;如打印p&#xff0c;删除d&#xff0c;插入i等 Windows&#xff1a;​ Linux&#xff1a; vim vi gedit nano emacs 一、sed介绍 1. sed的工作流程 首先sed把当前正在处理的行保存…

在线生成含logo的彩色二维码工具

具体请前往&#xff1a;在线二维码生成工具--可生成指定大小和颜色的彩色二维码图片,并支持Logo

03 前后端数据交互【小白入门SpringBoot + Vue3】

项目笔记&#xff0c;教学视频来源于B站青戈 https://www.bilibili.com/video/BV1H14y1S7YV 前两个笔记。是把前端页面大致做出来&#xff0c;接下来&#xff0c;把后端项目搞一下。 后端项目&#xff0c;使用IDEA软件、jdk1.8、springboot2.x 。基本上用的是稳定版。 还有My…

视频封面:从视频中提取封面,轻松制作吸引人的视频

在当今的数字时代&#xff0c;视频已成为人们获取信息、娱乐和交流的重要方式。一个吸引人的视频封面往往能抓住眼球&#xff0c;提高点击率和观看率。今天将介绍如何从视频中提取封面&#xff0c;轻松制作吸引人的视频封面。 一、准备素材选择合适的视频片段 首先&#xff0…

04 后端增删改查【小白入门SpringBoot + Vue3】

项目笔记&#xff0c;教学视频来源于B站青戈 https://www.bilibili.com/video/BV1H14y1S7YV 保证前面的都功能都实现后&#xff0c;接着往下走。 查 分页 接下来&#xff0c;实现前端页面分页功能。 前端分页组件 打开elementplus官网&#xff0c;找到合适的分页组件&…

使用Nodejs搭建简单的Web网页并实现公网访问

目录 前言 1. 安装Node.js环境 2. 创建Node.js应用 3. 安装Cpolar内网穿透实现公网访问Nodejs服务 3.1 注册cpolar账号 3.2 下载cpolar客户端 3.3 创建隧道映射本地端口 4. 固定公网远程地址 前言 Node.js是建立在谷歌Chrome的JavaScript引擎(V8引擎)的Web应用程序框架…

【Linux】动静态库的使用与软链接的结合

文章目录 前言一、静态库1.静态库的创建2.静态库的链接3.将库进行打包4.链接方法&#xff1a;1.直接链接2.拷贝到系统路径里面3.采用软链接方法 二、动态库1.解决加载找不到动态库的方法1.直接拷贝2.建立软链接3.建立自己的动态路径配置文件 2.为什么动态库权限需可执行而静态库…

nrm的安装以及使用

1&#xff0c;什么是nrm nrm 是一个 npm 源管理器&#xff0c;允许你快速地在 npm源间切换。 什么意思呢&#xff0c;npm默认情况下是使用npm官方源&#xff08;使用npm config ls命令可以查看&#xff09;&#xff0c;在国内用这个源肯定是不靠谱的&#xff0c;一般我们都会…

518. 零钱兑换II(完全背包问题)

题目 题解 class Solution:def change(self, amount: int, coins: List[int]) -> int:# 状态定义&#xff1a;dp[i][j]表示用前i种硬币&#xff0c;刚好凑齐面额j的方法有多少dp [[0 for i in range(amount1)] for j in range(len(coins)1)]# base casefor i in range(len…

02 elementplus前端增删改查【小白入门SpringBoot+Vue3】

视频教程来源于 B站青戈 https://www.bilibili.com/video/BV1H14y1S7YV 只用elementplus&#xff0c;学点增删改查&#xff0c;还没有于后端连接起来&#xff0c;具体在下一篇 搭建一个小页面&#xff0c;显示数据 补充&#xff1a;webstorm格式化代码&#xff0c;修改了快捷…

国学---佛系算吉凶~

佛系算吉凶咯~&#xff0c;正经走访深山庙宇&#xff0c;前辈老人&#xff0c;经过调研后&#xff0c;搭建的轻衍计算模型&#xff0c;团队对国学的初次信息化尝试。 共享给有需要的朋友&#xff0c;准不准没关系&#xff0c;开心最重要。 后续还有财富&#xff0c;事业&…