OSG编程指南<十八>:OSG读写保存文件及对中文字符的支持

  基本几何体的绘制只适用于简单的编程,当场景中需要加载一个很复杂的模型时,还是需要从外部导入。osgDB 库
提供了读取二维图像和三维模型的接口,同时,也管理着第三方插件系统,以实现对不同格式文件的读取。

1、OSG 支持的文件格式

  由于 OSG 包含庞大的第三方插件库,所以 OSG 支持的文件格式也非常多,如各种三维模型、图
片和视频等文件,这也是 OSG 的一大优势,它可以满足各行各业的需求。

1.1 三维模型文件格式

  OSG支持的三维模型文件格式类型如下表所示。
在这里插入图片描述

1.2 打包及网络传输格式

  OSG支持的打包及网络传输文件格式类型如下表所示。
在这里插入图片描述

1.3 字体文件格式

  OSG支持的字体文件格式类型如下表所示。
在这里插入图片描述

1.4 伪插件文件格式

  OSG支持的伪插件文件格式类型如下表所示。
在这里插入图片描述

2、.osg 文件和.ive 文件

  OpenSceneGraph 支持如下两种本地文件格式:

.osg OpenSceneGraph native ascii format
.ive OpenSceneGraph native binary format。

2.1 .osg 文件

  .osg 文件格式是标准的 ASCII 文本,它并不是一种可以实现高效存储和读取的数据结构,但它是一种极好的调试工具。由于它是一种文本文件,所以清晰可读。在应用程序开发时,如果遇到了意料不到的渲染结果,可以将渲染结果保存为.osg 文件,然后对其进行手动的修改从而纠正错误,这对实际调试程序来说是非常方便的。

2.2 .ive 文件

  为了加快模型导入和显示的速度,每个视景驱动软件都有自己的二进制格式,如 Vega 的 FST、Vega Prime 的 VSB 和 Performer 的 PFB 等。OSG 里面的就是 IVE,IVE 就是为了性能而生的,它把场景树存成二进制文件。所以在分发应用程序时,可以把模型转为 IVE,并把纹理打包进去(osgconvmyfile.flt——compressed myfile.ive),这样既可以达到保护自己劳动成果的目的,还能提高应用程序的性能。

3、文件读写的流程

  osgDB 库允许用户程序加载、使用和写入 3D 数据库,它采用插件管理的架构,可以支持大量常见的 2D 图形和 3D 模型文件格式。osgDB 负责维护插件的信息注册表,并负责检查将要被载入的 OSG插件接口的合法性。OSG 可以支持自己的文件格式。.osg 文件是对场景图形的一种无格式 ASCII 码文本描述,而.osga文件是一组.osg 文件的有序集合。osgDB 库包含了以上文件格式的支持代码。另外,OSG 还支持一种二进制的.ive 格式。

  由于大型的 3D 地形数据库通常是多段数据块的组合体。因此,应用程序从文件中读取各部分数据库信息时,需要在不干扰当前渲染的前提下以后台线程的方式进行,osgDB::DatabasePager 提供了这样的功能。

3.1 文件的读取与保存

3.1.1 文件的读取

  文件读取的简单代码如下:

#include<osgDB/ReadFile>
osg::ref_ptr<osg::Node> node = osgDB::readNodeFile("cow.osg");
osg::ref_ptr<osg::Image> image = osgDB::readImageFile("lz.rgb");

  上面代码中,函数类的参数可以包含绝对路径或相对路径。如果是绝对路径,OSG 会去指定的位置搜索该文件;如果是相对路径,就要复杂一点了,它会从数据路径列表中来搜索文件。在本章会提到环境变量 OSG_FILE_PATH,这个是在 OSG 中预先定义的。在用户的应用程序中,除了设置环境变量以外,还可以手动添加数据文件路径列表。有如下两种方式:

(1)第一种方式的程序代码如下:

osgDB::FilePathList pathList = osgDB::getDataFilePathList();
pathList.push_back("…");
pathList.push_back("…");
osgDB::setDataFilePathList(pathList);

(2)第二种方式的程序代码如下:

osgDB::Registry::instance()->getDataFilePathList().push_back(newpath );

  osgDB::Registry 是一个单态类(singleton),因此,要调用函数 getDataFilePathList(),就需要使用单态类的实例。osgDB::FilePathList 就是一个简单的 std::deque< std::string >。

  文件读取失败的原因可能很多,读者可以通过设置调试信息 NOTIFY 来确定,当再次读取时,就会提示相关错误或者警告信息。很多时候可能是因为找不到插件或者文件不存在造成的,这也要根据输出信息来具体确定到底是什么错误。

3.1.2.文件的保存

文件保存的简单代码如下:

#include <osgDB/WriteFile>
osgDB::writeNodeFile(*node, "saved.osg");

  文件保存失败的原因也很多。读者可以通过设置调试信息 NOTIFY 来确定,当再次保存时,就会提示相关错误或者警告信息。很多时候可能是因为找不到插件或者该扩展名不支持造成的,这也要根据输出信息具体来确定到底是什么错误。需要注意的是,当 OSG 保存文件时,如果本地有同名文件,会覆盖并不会给出任何警告的信息,所以在保存时需要自行检查本地文件是否存在同名的。

3.2 文件读写进度

  面对海量数据时,读取可能需要耗费很长的时间,这时有必要显示正在读取的数据量和读取进度等信息。下面先介绍 C++流缓冲的一些知识。众所周知,C++标准库的 iostream 提供了如下 3 种形式的缓冲:

不带缓冲区,这样与 fwrite 等操作一致。
空间自动管理的缓冲区,下一个可写位置总是为末边界+1。
外置的缓冲区,如定义局部字符串数组,并将此区域传递给 iostream。

  在带缓冲区的情况下,iostream 本身不具体负责其相关功能,而把此任务交给了 basic_streambuf及其派生类,常见的如 basic_filebuf(用于文件操作的缓冲)和 basic_stringbuf(用于字符串操作的缓冲区)。basic_iostream 的两大分支 basic_istream 和 basic_ostream 都是 basic_streambuf 的友元类,而basic_iostream 包含了一个 basic_streambuf 的派生类的实例,该实例可以通过 basic_iostream 的成员函数 rdbuf()来获取,并通过构造函数传入。我们经常使用的流操作符“<<”实际上是通过调用 basic_streambuf 的相关操作实现的。

  要控制标准输入/输出流的缓冲区行为,必须生成自己的 basic_streambuf 派生类,并生成一个该类的对象,传递给标准输入/输出流。由于基类 basic_streambuf 的很多功能没有具体实现,为了方便操作,我们需要从 basic_filebuf 或basic_stringbuf 派生。在第 6.2.5 节的示例中,显示了读取一个.osg 文件的读取进度。前面已经讲到,.osg 文件是一种 ASCII 文件,因此,.osg 支持数据流操作。在 OSG 支持的文件中,只有.osg、.ive 及一些图像文件支持流操作,都可以用数据流操作来显示读取进度。

  所有的 osgDB 插件都可以使用 osgDB::readNodeFile()、osgDB::writeNodeFile()和 osgDB::readImageFile()之类的函数来执行文件的读取和写入操作,其中一些插件还允许使用标准数据流来执行一些低层级的操作,如从用户自定义的数据流中读写数据。由于 C++标准模板库(STL)的便利性,用户可以编写自定义的数据流缓存类来跟踪 uflow()方法,从而获取当前读入的字节数。用户定义了正确的数据流缓存类(如例子程序所示)后,就可以按照如下的步骤来读取文件:

(1)给出文件名,并搜索用于读取该文件的插件,也就是调用 osgDB::Registry::instance()->getReaderWriterForExtension(),传递文件扩展名作为该函数的输入参数,并保存返回的 osgDB::ReaderWriter对象。
(2)创建自定义数据流缓存类的实例,将文件名传递给类的构造函数。此时函数将按照类似于standard std::basic_filebuf<>的方法打开文件。
(3)通过自定义类的实例创建 std::istream 对象。
(4)调用步骤(1)中获取的 ReaderWriter 对象的 readNode()方法,并将步骤(3)创建的 istream对象作为其输入参数。此时,OSG 将读入文件,同时调用自定义类的 uflow()方法,该方法将负责显示读入字节的进度信息。

3.1.2.读取进度示例
在这里插入图片描述

#include <windows.h>
#include <iostream>
#include <fstream>
#include <osgViewer/Viewer>
#include <osgDB/ReaderWriter>
#include <osgDB/ReadFile>
#include <osgDB/FileNameUtils>
#include <osgDB/FileUtils>
#include <osgDB/Registry>
#include <osgUtil/Optimizer>

#pragma comment(lib, "OpenThreadsd.lib")
#pragma comment(lib, "osgd.lib")
#pragma comment(lib, "osgDBd.lib")
#pragma comment(lib, "osgUtild.lib")
#pragma comment(lib, "osgGAd.lib")
#pragma comment(lib, "osgViewerd.lib")
#pragma comment(lib, "osgTextd.lib")

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();
		int nProgres = 0;
		std::cout << nProgres << "%%" << std::endl;

		while (pos < _length)
		{
   
			pos = _fin->tellg();

			int nTemp = (int)(100.0 * pos / _length);
			if (nTemp > nProgres && nTemp%5 == 0)
			{
   
				std::cout << nTemp << "%%" << std::endl;
				nProgres = nTemp;
			}
		}

		std::cout << "读取完成!" << 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 istream(fileName.c_str(), std::ios::in | std::ios::binary);
		CRenderingThread crt(&istream);
		crt.startThread();
		if (istream)
		{
   
			std::cout << "正在读取模型文件,进度如下:" << std::endl;

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

			return rr;
		}

		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"));
	viewer->setUpViewInWindow(600, 600, 1000, 800);

	return viewer->run();
}

3.3 OSG中文文件名及中文路径问题

  在读写文件时,经常会遇到这样的情况,如果文件名包含中文路径就会导致无法读取或读取失败。因为我们使用的是 unicode 编码位模式,而 OSG 的库函数只支持 ANSI 编码或多字节编码。其实这是符合 C 语言规范的,因为 C 标准并不支持 unicode,只是很多 C 的实现将宽字符用 unicode 的位模式表示。这时我们需要通过 setlocale 函数将 unicode 编码的宽字符转换成一种可以支持的编码。Setlocale 函数的语法格式如下:

char * setlocale ( int category, const char * locale );

  该函数的返回值是字符串,函数种类是操作系统与环境。该函数用来配置地域的信息,设置当前程序使用的本地化信息。参数 category 可以设置为如下数值。

LC_ALL:包括所有选项的功能。
LC_COLLATE:配置字符串比较,PHP 目前尚未实作出来本项。
LC_CTYPE:配置字符类别及转换,如全变大写 strtoupper()。
LC_MONETARY:配置金融货币,PHP 目前尚未实作。
LC_NUMERIC:配置小数点后的位数。
LC_TIME:配置时间日期格式,与 strftime()合用。

  而参数 locale 若是空字符串,则会使用系统环境变量 locale。若 locale 为 0(NULL),则不会改变地域化配置,返回当前的地域值,若系统尚未实作则返回 false。

只需要做如下设置就可以实现编码格式的转换:

setlocale( LC_ALL, "Chinese-simplified" );
setlocale( LC_ALL, "chs" );
setlocale( LC_ALL, "ZHI" );
setlocale( LC_ALL, ".936" );

  关于字符编码转换,其实有很多好用的开源工具包可用,如著名的 iconv。平时积累编码方面的知识对学习 OSG 是非常有帮助的,而且在程序开发时计算机编码也是一个非常重要的方面。

//创建一个节点,读取牛的模型
osg::ref_ptr<osg::Node> node1 = new osg::Node();
/*很关键的一个函数,用于识别 unicode 中文字符 */
setlocale( LC_ALL, "chs" );
node1 = osgDB::readNodeFile("牛.ive");

最近更新

  1. docker php8.1+nginx base 镜像 dockerfile 配置

    2023-12-06 13:12:07       94 阅读
  2. Could not load dynamic library ‘cudart64_100.dll‘

    2023-12-06 13:12:07       100 阅读
  3. 在Django里面运行非项目文件

    2023-12-06 13:12:07       82 阅读
  4. Python语言-面向对象

    2023-12-06 13:12:07       91 阅读

热门阅读

  1. ajax检查密码是否一致

    2023-12-06 13:12:07       56 阅读
  2. Python字符串格式化

    2023-12-06 13:12:07       62 阅读
  3. Redis远程字典服务

    2023-12-06 13:12:07       58 阅读
  4. OpenHarmony 4.0 Release 编译及报错

    2023-12-06 13:12:07       45 阅读
  5. python文件docx转pdf

    2023-12-06 13:12:07       58 阅读
  6. 高防CDN技术的崛起与网络安全的演进

    2023-12-06 13:12:07       60 阅读