Введение в OpenSceneGraph: Система плагинов. Импорт и экспорт данных
Где-то в предыдущих уроках уже говоилось о том, что OSG поддерживает загрузку разного рода ресурсов типа растровых изображений, 3D-моделей различных форматов, или, например, шрифты через собственную систему плагинов. Плагин OSG является отдельным компонентом, расширяющим функционал движка и обладающий интерфейсом, стандартизированным в пределах OSG. Плагин реализуется как динамическая разделяемая библиотека (dll в Windows, so в Linux и т.д). Имена библиотек плагинов соотвествуют определенному соглашению
osgdb_<расширение файла>.dll
то есть в имени плагина всегда присутсвует префикс osgdb_. Расширение файла указывает движку какой плагин следует использовать для загрузки файла с данным расширением. Например, когда мы пишем в коде функцию
osg::ref_ptr<osg::Node> model = osgDB::readNodeFile("cessna.osg");
движок видит расширение osg и загружает плагин с именем osgdb_osg.dll (или osgdb_osg.so в случает Linux). Код плагина выполняет всю черную работу, возвращая нам указатель на ноду, описывающую модель цессны. Аналогичным образом, попытка загрузки изображения формата PNG
osg::ref_ptr<osg:Image> image = osgDB::readImageFile("picture.png");
приведет к тому, что будет загружен плагин osgdb_png.dll, в котором реализован алгоритм чтения данных из картинки в формате PNG и помещение этих данных в объект типа osg::Image.
Все операции по работе с внешними ресурсами реализуются функциями библиотеки osgDB, с которой мы неизменно линкуем программы из примера в пример. Эта библиотека опирается на систему плагинов OSG. На сегодняшний день в комплек OSG входит множество плагинов, обеспечивающих работу с большинством используемых на практике форматов изображений, 3D-моделей и шрифтов. Плагины обеспечивают как чтение данных (импорт) определенного формата, так и, в большинстве случаем запись данных в файл необходимого формата (экспорт). На систему плагинов опирается в частности утилита osgconv, позволяющая преобразовать данные из одного формата в другой, например
$ osgconv cessna.osg cessna.3ds
легко и непринужденно коневертирует osg-модель цессны в формат 3DS, который потом может быть импортирован в 3D-редактор, например в Blender (кстати сказать для Blender существует расширение для работы с osg непосредственно)
Существует официальный перечень стандартных плагинов OSG с описанием их назначения, но он длинный и мне лень приводить его здесь. Проще заглянуть по пути установки библиотеки в папку bin/ospPlugins-x.y.z, где x, y, z - номер версии OSG. Из имени файл плагина легко понять какой формат он обрабатывает.
Если OSG собран компилятором MinGW, то к стандартному имени плагина добавляется дополнительный префикс mingw_, то есть имя будет выглядеть так
mingw_osgdb_<расширение файла>.dll
Версия плагина, собранная в конфигурации DEBUG дополнительно снабжается суффиксом d в конце имени, то есть формат будет такой
osgdb_<расширение файла>d.dll
или
mingw_osgdb_<расширение файла>d.dll
при сборке MinGW.
Плагины песвдо-загрузчики
Некоторые лпагины OSG выполняю функции так называемых псевдо-загрузчиков - это означает что они не привязаны к конкретному расширению файла, но, путем добавления суффикса в конец имени файла, можно указать какой плагин необходимо использовать для загрузки данного файла, например
$ osgviewer worldmap.shp.ogr
В данном случае реальное имя файла на диске worldmap.shp - этот файл хранит в себе карту мира в формате ESRI shapefile. Суффикс .ogr указзывает библиотеке osgDB использовать плагин osgdb_ogr для загрузки этого файла; в противном случае будет использован плагин osgdb_shp.
Другим хорошим примером является плагин osgdb_ffmpeg. Библиотека FFmpeg поддерживает свыше 100 различных кодеков. Для чтения любого из них мы можем просто дабавить суффикс .ffmpeg после имени медиафайла.
Вдобавок к этому, некоторые песевдо-загрузчики позволяют передавать через суффикс ряд параметров, влияющих на состояние загружаемого объекта, и с этим мы уже сталкивались в одном из примеров с анимацией
node = osgDB::readNodeFile("cessna.osg.0,0,90.rot");
Строка 0,0,90 указывает плагину osgdb_osg параметры начальной ориентации загружаемой модели. Некоторые псевдо-загрузчики требуют для своей работы задания совершенно специфичных параметров.
API для разработки сторонних плагинов
Совершенно логично, если после всего прочитанного у вас возникла мысль о том, что наверняка не составит труда написать собственный плагин к OSG, который будет позволять импортировать нестандартный формат 3D-моделей или изображений. И это верная мысль! Механизм плагинов как раз и предназначен для расширения функциональности движка без изменения самого OSG. Чтобы понять основные принципы написания плагина, попробуем реализовать простейший пример.
Разработка плагина заключается в расширении виртуального интерфейса чтения-записи данных, предоставляемого OSG. Данный функционал обеспечивается виртуальным классом osgDB::ReaderWriter. Этот класс предоставляет ряд виртуальных методов, переопределяемых разработчиком плагина
Метод | Описание |
---|---|
supportsExtensions() | Принимает два строковых параметра: расширение файла и описание. Метод всегда вызывается в конструкторе подкласса |
acceptsExtension() | Возвращает true если расширение, переданное в качестве аргумента поддерживается плагином |
fileExists() | Позволяет определить, существует ли данный файл (путь передается в качестве параметра) на диске (возвращает true в случае успеха) |
readNode() | Принимает имя файла и опции в виде объекта osgDB::Option. Функции по чтению данных из файла реализуются разработчиком |
writeNode() | Принимает имя ноды, желаемое имя файла и опции. Функции по записи данных на диск реализуются разработчиком |
readImage() | Чтение данных о растровом изображении с диска |
writeImage() | Запись растрового изображения на диск |
Реализация методf readNode() может быть описана следующим кодом
osgDB::ReaderWriter::ReadResult readNode(
const std::string &file,
const osgDB::Options *options) const
{
// Проверяем что расширение файла поддерживается и файл существует
bool recognizableExtension = ...;
bool fileExists = ...;
if (!recognizableExtension)
return ReadResult::FILE_NOT_HANDLED;
if (!fileExists)
return ReadResult::FILE_NOT_FOUND;
// Конструируем подграф сцены в соответсвии со спецификацией загружаемого формата
osg::Node *root = ...;
// В случае ошибок в процессе выполения каких-либо операций возвращаем сообщения об ошибке.
// В случае успеха - возвращаем корневую ноду подграфа сцены
bool errorInParsing = ...;
if (errorInParsing)
return ReadResult::ERROR_IN_READING_FILE;
return root;
}
Немного удивляет, что вместо указателя на ноду графа сцены метод возвращает тип osgDB::ReaderWriter::ReadResult. Этот тип - объект результата чтения, и он может использоваться как контейнер узла, изображение, перечислитель состояния (например FILE_NOT_FOUND), другой специальный объект или даже как строка сообщения об ошибке. Он имеет множество неявных конструкторов для реализации описанных функций.
Другим полезным классом является osgDB::Options. Он может позволяет задать или получить строку опций загрузки методами setOptionString() и getOptionString(). Допускается так же передача данной строки в конструктор этого класса в качестве аргрумента.
Разработчик может управлять поведением плагина, задавая настройки в строке параметров, переданной при загрузке объекта, например таким способом
// Параметры не передаются
osg::Node* node1 = osgDB::readNodeFile("cow.osg");
// Параметры передаются через строку string
osg::Node* node2 = osgDB::readNodeFile("cow.osg", new osgDB::Options(string));
Обработка потока данных в плагине OSG
Базовый класс osgDB::ReaderWriter включает в себя набор методов, обрабатывающих данные потоков ввода/вывода предоставляемых стандартной библиотекой C++. Есдинственное отличие этих методов чтения/записи от рассмотренных выше в том, что вместо имени файла они принимают на вход потоки ввода std::istream & или поток вывода std::ostream &. Использование файлового потока ввода/выода всегда предпочтительнее использования имени файла. Для выполнения операций чтения файла мы можем использовать следующий дизайн интерфейса:
osgDB::ReaderWriter::ReadResult readNode(
const std::string &file,
const osgDB::Options *options) const
{
...
osgDB::ifstream stream(file.c_str(), std::ios::binary);
if (!stream)
return ReadResult::ERROR_IN_READING_FILE;
return readNode(stream, options);
}
...
osgDB::ReaderWriter::ReadResult readNode(
std::istream &stream,
const osgDB::Options *options) const
{
// Формируем граф сцены в соотвествии с форматом файла
osg::Node *root = ...;
return root;
}
После реализации плагина мы можем использовать штатные функции osgDB::readNodeFile() и osgDB::readImageFile() для загрузки моделей и изображений, просто указав путь к файлу. OSG сам найдет и загрузит написаный нами плагин.