OpenSceneGraph: Plugin System

  • Tutorial
image

Introduction


Somewhere in previous lessons, it has already been said that OSG supports downloading various kinds of resources such as bitmaps, 3D models of various formats, or, for example, fonts through its own plugin system. The OSG plugin is a separate component that extends the functionality of the engine and has an interface standardized within OSG. The plugin is implemented as a dynamic shared library (dll on Windows, so on Linux, etc.). Plugin library names follow a specific convention.

osgdb_<расширение файла>.dll

that is, the prefix osgdb_ is always present in the name of the plugin. The file extension indicates the engine which plugin should be used to download a file with this extension. For example, when we write a function in code

osg::ref_ptr<osg::Node> model = osgDB::readNodeFile("cessna.osg");

the engine sees the osg extension and loads a plugin named osgdb_osg.dll (or osgdb_osg.so for Linux). The plug-in code does all the dirty work, returning to us a pointer to a node describing the model of Cessna. Similarly, trying to load a PNG image

osg::ref_ptr<osg:Image> image = osgDB::readImageFile("picture.png");

will cause the osgdb_png.dll plugin to be loaded, which implements an algorithm for reading data from a picture in PNG format and putting this data into an object of type osg :: Image.

All operations on working with external resources are implemented by the functions of the osgDB library, with which we invariably link programs from example to example. This library relies on the OSG plugin system. Today, OSG comes with many plug-ins that work with most image formats, 3D models, and fonts used in practice. Plug-ins provide both data reading (import) of a certain format, and, in most cases, writing data to a file of the required format (export). The system of plug-ins relies in particular on the osgconv utility, which allows you to convert data from one format to another, for example

$ osgconv cessna.osg cessna.3ds

Easily and naturally converts the Cessna's osg-model to the 3DS format, which can then be imported into a 3D editor, for example, in Blender (by the way for Blender there is an extension for working with osg directly )



There is an official list of standard OSG plug-ins describing their purpose, he is long and lazy to bring him here. It is easier to look at the installation path of the library in the bin / ospPlugins-xyz folder, where x, y, z is the version number of OSG. From the name of the plugin file, it is easy to understand what format it processes.

If OSG is compiled by the MinGW compiler, then an additional mingw_ prefix is ​​added to the standard plugin name, that is, the name will look like this

mingw_osgdb_<расширение файла>.dll

The version of the plug-in compiled in the DEBUG configuration is additionally supplied with the suffix d at the end of the name, that is, the format will be

osgdb_<расширение файла>d.dll

or

mingw_osgdb_<расширение файла>d.dll

when building MinGW.

1. Pseudo Downloaders


Some OSG plug-ins perform the functions of so-called pseudo-downloaders - this means that they are not tied to a specific file extension, but by adding a suffix to the end of the file name, you can specify which plug-in to use to download this file, for example

$ osgviewer worldmap.shp.ogr

In this case, the real name of the file on the worldmap.shp disk - this file stores the world map in the ESRI shapefile format. The .ogr suffix tells the osgDB library to use the osgdb_ogr plugin to load this file; otherwise, the osgdb_shp plugin will be used.

Another good example is the osgdb_ffmpeg plugin. The FFmpeg library supports over 100 different codecs. To read any of them, we can simply add the suffix .ffmpeg after the name of the media file.

In addition to this, some pseudo-loaders allow you to pass through a suffix a number of parameters that affect the state of the loaded object, and we have already encountered in one of the examples with animation

node = osgDB::readNodeFile("cessna.osg.0,0,90.rot");

Line 0, 0.90 specifies the osgdb_osg plugin for the parameters of the initial orientation of the loaded model. Some pseudo-loaders require for the operation of the job very specific parameters.

2. API for developing third-party plug-ins


It is quite logical if, after all you have read, you had the idea that it is certainly not difficult to write your own OSG plugin, which will allow you to import non-standard format of 3D models or images. And this is the right idea! The plug-in mechanism is precisely designed to expand the functionality of the engine without changing OSG itself. To understand the basic principles of writing a plugin, we will try to implement the simplest example.

The development of the plug-in is to extend the virtual data read / write interface provided by OSG. This functionality is provided by the virtual class osgDB :: ReaderWriter. This class provides a number of virtual methods that the plugin developer overrides.
MethodDescription
supportsExtensions ()It accepts two string parameters: file extension and description. The method is always called in the constructor of a subclass.
acceptsExtension ()Returns true if the extension passed in as an argument is supported by the plugin.
fileExists ()Allows you to determine whether this file (the path is passed as a parameter) on the disk (returns true if successful)
readNode ()Accepts the file name and options as an osgDB :: Option object. Functions for reading data from a file are implemented by the developer.
writeNode ()Accepts the name of the node, the desired file name and options. Functions for writing data to disk are implemented by the developer.
readImage ()Reading raster image data from disk
writeImage ()Burn bitmap to disk

The implementation of the readNode () method can be described with the following code.

osgDB::ReaderWriter::ReadResult readNode(
	conststd::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;
}

It is a little surprising that instead of a pointer to the node of the scene graph, the method returns the type osgDB :: ReaderWriter :: ReadResult. This type is a read result object, and it can be used as a node container, an image, a state enumerator (for example, FILE_NOT_FOUND), another special object, or even as an error message string. It has many implicit constructors for implementing the described functions.

Another useful class is osgDB :: Options. It can allow you to set or get a string of boot options with the setOptionString () and getOptionString () methods. It is also allowed to pass this string to the constructor of this class as an argument.

The developer can control the behavior of the plug-in by specifying the settings in the parameter line passed when the object is loaded, for example in this way

// Параметры не передаются
osg::Node* node1 = osgDB::readNodeFile("cow.osg"); 
// Параметры передаются через строку string
osg::Node* node2 = osgDB::readNodeFile("cow.osg", new osgDB::Options(string)); 

3. Processing data stream in OSG plugin


The base class osgDB :: ReaderWriter includes a set of methods that process the I / O stream data provided by the C ++ standard library. The only difference between these read / write methods and the ones discussed above is that instead of the file name, they accept input streams std :: istream & or output stream std :: ostream &. Using a file I / O stream is always preferable to using a file name. To perform file read operations, we can use the following interface design:

osgDB::ReaderWriter::ReadResult readNode(
	conststd::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;
}

After implementing the plugin, we can use the osgDB :: readNodeFile () and osgDB :: readImageFile () functions to load models and images, simply by specifying the path to the file. OSG will find and download the plugin written by us.

4. Write your own plugin



So, no one bothers us to come up with our own three-dimensional geometry data storage format, and we will come up with it

piramide.pmd

vertex:  1.0  1.0 0.0
vertex:  1.0 -1.0 0.0
vertex: -1.0 -1.0 0.0
vertex: -1.0  1.0 0.0
vertex:  0.0  0.0 2.0
face: 0 1 2 3
face: 0 3 4
face: 1 0 4
face: 2 1 4
face: 3 2 4

Here at the beginning of the file is a list of vertices with their coordinates. Vertex indices go in order, starting from zero. After the list of vertices is a list of faces. Each face is set by the list of indexes of the vertices from which it is formed. As you can see nothing complicated. The task is to read this file from disk and form a three-dimensional geometry on its basis.

5. Configuring the plugin project: features of the build script


If earlier we collected applications, now we have to write a dynamic library, and not just a library, but an OSG plugin that satisfies certain requirements. We will begin to fulfill these requirements from the project build script, which will look like this

plugin.pro

TEMPLATE = lib
CONFIG += plugin
CONFIG += no_plugin_name_prefix
TARGET = osgdb_pmd
win32-g++: TARGET = $$join(TARGET,,mingw_,)
win32 {
    OSG_LIB_DIRECTORY = $$(OSG_BIN_PATH)
    OSG_INCLUDE_DIRECTORY = $$(OSG_INCLUDE_PATH)
    DESTDIR = $$(OSG_PLUGINS_PATH)
    CONFIG(debug, debug|release) {
        TARGET = $$join(TARGET,,,d)
        LIBS += -L$$OSG_LIB_DIRECTORY -losgd
        LIBS += -L$$OSG_LIB_DIRECTORY -losgViewerd
        LIBS += -L$$OSG_LIB_DIRECTORY -losgDBd
        LIBS += -L$$OSG_LIB_DIRECTORY -lOpenThreadsd
        LIBS += -L$$OSG_LIB_DIRECTORY -losgUtild
    } else {
        LIBS += -L$$OSG_LIB_DIRECTORY -losg
        LIBS += -L$$OSG_LIB_DIRECTORY -losgViewer
        LIBS += -L$$OSG_LIB_DIRECTORY -losgDB
        LIBS += -L$$OSG_LIB_DIRECTORY -lOpenThreads
        LIBS += -L$$OSG_LIB_DIRECTORY -losgUtil
    }
    INCLUDEPATH += $$OSG_INCLUDE_DIRECTORY
}
unix {
    DESTDIR = /usr/lib/osgPlugins-3.7.0
    CONFIG(debug, debug|release) {
        TARGET = $$join(TARGET,,,d)
        LIBS += -losgd
        LIBS += -losgViewerd
        LIBS += -losgDBd
        LIBS += -lOpenThreadsd
        LIBS += -losgUtild
    } else {
        LIBS +=  -losg
        LIBS +=  -losgViewer
        LIBS +=  -losgDB
        LIBS +=  -lOpenThreads
        LIBS += -losgUtil
    }
}
INCLUDEPATH += ./include
HEADERS += $$files(./include/*.h)
SOURCES += $$files(./src/*.cpp)

Separate nuances we will disassemble in more detail

TEMPLATE = lib

means that we will build the library. To prevent the generation of symbolic links, with the help of which conflicts of library versions are resolved in * nix systems, we indicate to the build system that this library will be a plugin, that is, it will be loaded into memory on the fly

CONFIG += plugin

Next, we exclude the generation of the lib perfix, which is added when using compilers of the gcc family and is taken into account by the runtime environment when loading the library

CONFIG += no_plugin_name_prefix

Set the name of the library file

TARGET = osgdb_pmd

where pmd is the file extension of the invented 3D model format. Next, be sure to indicate that in the case of the MinGW assembly, the prefix mingw_ must be added to the name

win32-g++: TARGET = $$join(TARGET,,mingw_,)

Specify library build path: for Windows

DESTDIR = $$(OSG_PLUGINS_PATH)

for linux

DESTDIR = /usr/lib/osgPlugins-3.7.0

For Linux, with this indication of the path (which is undoubtedly a crutch, but I haven’t yet found another solution) we give write access to the specified folder with OSG plug-ins from a regular user

# chmod 666 /usr/lib/osgPlugins-3.7.0

All other build settings are similar to those used earlier for building sample applications.

6. Configuring the plugin project: debug mode features


Since this project is a dynamic library, then there must be a program that loads this library during its execution. It can be any application that uses OSG and in which the function call will occur

node = osdDB::readNodeFile("piramide.pmd");

In this case, our plug-in will be loaded. In order not to write such a program on our own, we will use the ready-made solution - the standard osgviewer viewer included in the engine delivery set. If the console run

$ osgviewer piramide.pmd

this will also trigger the plug-in. In the project launch settings, specify the path to osgviewerd, specify the directory where the piramide.pmd file is located as the working directory, and specify the same file in the command line options osgviewer



Now we can run the plugin and debug it directly from the IDE QtCreator.

6. We implement the plug-in framework


This example to some extent summarizes the knowledge that we have already learned about OSG from previous lessons. When writing a plugin we have to

  1. Select a data structure to store information about the geometry of the model, read from the model file
  2. Read and parse (parse) the file with the model data
  3. Correctly adjust the geometry object osg :: Drawable according to the data read from the file
  4. Build a scene subgraph for the loaded model

So, according to tradition, I will give the source code of the plugin entirely

Plugin osgdb_pmd
main.h

#ifndef		MAIN_H#define		MAIN_H#include<osg/Geometry>#include<osg/Geode>#include<osgDB/FileNameUtils>#include<osgDB/FileUtils>#include<osgDB/Registry>#include<osgUtil/SmoothingVisitor>//------------------------------------------------------------------------------////------------------------------------------------------------------------------structface_t
{std::vector<unsignedint> indices;
};
//------------------------------------------------------------------------------////------------------------------------------------------------------------------structpmd_mesh_t
{
    osg::ref_ptr<osg::Vec3Array> vertices;
    osg::ref_ptr<osg::Vec3Array> normals;
    std::vector<face_t> faces;
    pmd_mesh_t()
        : vertices(new osg::Vec3Array)
        , normals(new osg::Vec3Array)
    {
    }
    osg::Vec3 calcFaceNormal(constface_t &face)const{
        osg::Vec3 v0 = (*vertices)[face.indices[0]];
        osg::Vec3 v1 = (*vertices)[face.indices[1]];
        osg::Vec3 v2 = (*vertices)[face.indices[2]];
        osg::Vec3 n = (v1 - v0) ^ (v2 - v0);
        return n * (1 / n.length());
    }
};
//------------------------------------------------------------------------------////------------------------------------------------------------------------------classReaderWriterPMD :public osgDB::ReaderWriter
{
public:
    ReaderWriterPMD();
    virtual ReadResult readNode(conststd::string &filename,
                                const osgDB::Options *options)const;
    virtual ReadResult readNode(std::istream &stream,
                                const osgDB::Options *options)const;
private:
    pmd_mesh_t parsePMD(std::istream &stream) const;
    std::vector<std::string> parseLine(conststd::string &line) const;
};
#endif

main.cpp

#include"main.h"//------------------------------------------------------------------------------////------------------------------------------------------------------------------
ReaderWriterPMD::ReaderWriterPMD()
{
    supportsExtension("pmd", "PMD model file");
}
//------------------------------------------------------------------------------////------------------------------------------------------------------------------
osgDB::ReaderWriter::ReadResult ReaderWriterPMD::readNode(
        conststd::string &filename,
        const osgDB::Options *options) const
{
    std::string ext = osgDB::getLowerCaseFileExtension(filename);
    if (!acceptsExtension(ext))
        return ReadResult::FILE_NOT_HANDLED;
    std::string fileName = osgDB::findDataFile(filename, options);
    if (fileName.empty())
        return ReadResult::FILE_NOT_FOUND;
    std::ifstream stream(fileName.c_str(), std::ios::in);
    if (!stream)
        return ReadResult::ERROR_IN_READING_FILE;
    return readNode(stream, options);
}
//------------------------------------------------------------------------------////------------------------------------------------------------------------------
osgDB::ReaderWriter::ReadResult ReaderWriterPMD::readNode(
        std::istream &stream,
        const osgDB::Options *options) const
{
    (void) options;
    pmd_mesh_t mesh = parsePMD(stream);
    osg::ref_ptr<osg::Geometry> geom = new osg::Geometry;
    geom->setVertexArray(mesh.vertices.get());
    for (size_t i = 0; i < mesh.faces.size(); ++i)
    {
        osg::ref_ptr<osg::DrawElementsUInt> polygon = new osg::DrawElementsUInt(osg::PrimitiveSet::POLYGON, 0);
        for (size_t j = 0; j < mesh.faces[i].indices.size(); ++j)
            polygon->push_back(mesh.faces[i].indices[j]);
        geom->addPrimitiveSet(polygon.get());
    }
    geom->setNormalArray(mesh.normals.get());
    geom->setNormalBinding(osg::Geometry::BIND_PER_PRIMITIVE_SET);
    osg::ref_ptr<osg::Geode> geode = new osg::Geode;
    geode->addDrawable(geom.get());
    return geode.release();
}
//------------------------------------------------------------------------------////------------------------------------------------------------------------------pmd_mesh_t ReaderWriterPMD::parsePMD(std::istream &stream) const
{
    pmd_mesh_t mesh;
    while (!stream.eof())
    {
        std::string line;
        std::getline(stream, line);
        std::vector<std::string> tokens = parseLine(line);
        if (tokens[0] == "vertex")
        {
            osg::Vec3 point;
            std::istringstreamiss(tokens[1]);
            iss >> point.x() >> point.y() >> point.z();
            mesh.vertices->push_back(point);
        }
        if (tokens[0] == "face")
        {
            unsignedint idx = 0;
            std::istringstreamiss(tokens[1]);
            face_t face;
            while (!iss.eof())
            {
                iss >> idx;
                face.indices.push_back(idx);
            }
            mesh.faces.push_back(face);
            mesh.normals->push_back(mesh.calcFaceNormal(face));
        }
    }
    return mesh;
}
//------------------------------------------------------------------------------////------------------------------------------------------------------------------std::stringdelete_symbol(conststd::string &str, char symbol){
    std::string tmp = str;
    tmp.erase(std::remove(tmp.begin(), tmp.end(), symbol), tmp.end());
    return tmp;
}
//------------------------------------------------------------------------------////------------------------------------------------------------------------------std::vector<std::string> ReaderWriterPMD::parseLine(conststd::string &line) const
{
    std::vector<std::string> tokens;
    std::string tmp = delete_symbol(line, '\r');
    size_t pos = 0;
    std::string token;
    while ( (pos = tmp.find(':')) != std::string::npos )
    {
       token = tmp.substr(0, pos);
       tmp.erase(0, pos + 1);
       if (!token.empty())
           tokens.push_back(token);
    }
    tokens.push_back(tmp);
    return tokens;
}
REGISTER_OSGPLUGIN( pmd, ReaderWriterPMD )


First we take care of the structures for storing the geometry data.

structface_t
{std::vector<unsignedint> indices;
};

- describes the face specified by the list of indexes of the vertices belonging to the given face. The model as a whole will be described by such a structure

structpmd_mesh_t
{
    osg::ref_ptr<osg::Vec3Array> vertices;
    osg::ref_ptr<osg::Vec3Array> normals;
    std::vector<face_t> faces;
    pmd_mesh_t()
        : vertices(new osg::Vec3Array)
        , normals(new osg::Vec3Array)
    {
    }
    osg::Vec3 calcFaceNormal(constface_t &face)const{
        osg::Vec3 v0 = (*vertices)[face.indices[0]];
        osg::Vec3 v1 = (*vertices)[face.indices[1]];
        osg::Vec3 v2 = (*vertices)[face.indices[2]];
        osg::Vec3 n = (v1 - v0) ^ (v2 - v0);
        return n * (1 / n.length());
    }
};

The structure consists of member variables for storing data: vertices - for storing an array of vertices of a geometric object; normals - an array of normals to the edges of the object; faces - the list of faces of the object. In the structure's constructor, smart pointers are initialized immediately.

pmd_mesh_t()
        : vertices(new osg::Vec3Array)
        , normals(new osg::Vec3Array)
{
}

In addition, the structure contains a method that allows to calculate the vector-normal to the edge calcFaceNormal () as a parameter that takes a structure that describes the face. We will not go into the details of the implementation of this method yet; we will analyze them somewhat later.

Thus, we decided on the structures in which we will store the geometry data. Now we will write the framework of our plug-in, namely, we will implement the heir class osgDB :: ReaderWriter

classReaderWriterPMD :public osgDB::ReaderWriter
{
public:
    ReaderWriterPMD();
    virtual ReadResult readNode(conststd::string &filename,
                                const osgDB::Options *options)const;
    virtual ReadResult readNode(std::istream &stream,
                                const osgDB::Options *options)const;
private:
    pmd_mesh_t parsePMD(std::istream &stream) const;
    std::vector<std::string> parseLine(conststd::string &line) const;
};

As recommended in the API description for plugin development, in this class we override methods for reading data from a file and converting it into a subgraph of the scene. We have two overloads for the readNode () method - one takes the file name as input, the other the standard input stream. The class constructor defines the file extensions supported by the plugin.

ReaderWriterPMD::ReaderWriterPMD()
{
    supportsExtension("pmd", "PMD model file");
}

The first overload of the readNode () method analyzes the correctness of the file name and the path to it, links the standard input stream to the file, and causes the second overload that performs the main work.

osgDB::ReaderWriter::ReadResult ReaderWriterPMD::readNode(
        conststd::string &filename,
        const osgDB::Options *options) const
{
    // Получаем расширение из пути к файлуstd::string ext = osgDB::getLowerCaseFileExtension(filename);
    // Проверяем, поддерживает ли плагин это расширениеif (!acceptsExtension(ext))
        return ReadResult::FILE_NOT_HANDLED;
    // Проверяем, имеется ли данный файл на дискеstd::string fileName = osgDB::findDataFile(filename, options);
    if (fileName.empty())
        return ReadResult::FILE_NOT_FOUND;
    // Связваем поток ввода с файломstd::ifstream stream(fileName.c_str(), std::ios::in);
    if (!stream)
        return ReadResult::ERROR_IN_READING_FILE;
    // Вызываем основную рабочую перегрузку метода readNode()return readNode(stream, options);
}

In the second overload, we implement an object formation algorithm for OSG

osgDB::ReaderWriter::ReadResult ReaderWriterPMD::readNode(
        std::istream &stream,
        const osgDB::Options *options) const
{
    (void) options;
    // Парсим файл *.pmd извлекая из него данные о геометрииpmd_mesh_t mesh = parsePMD(stream);
    // Создаем геометрию объекта
    osg::ref_ptr<osg::Geometry> geom = new osg::Geometry;
    // Задаем массив вершин
    geom->setVertexArray(mesh.vertices.get());
    // Формируем грани объектаfor (size_t i = 0; i < mesh.faces.size(); ++i)
    {
        // Создаем примитив типа GL_POLYGON с пустым списком индексов вершин (второй параметр - 0)
        osg::ref_ptr<osg::DrawElementsUInt> polygon = new osg::DrawElementsUInt(osg::PrimitiveSet::POLYGON, 0);
        // Заполняем индексы вершин для текущей граниfor (size_t j = 0; j < mesh.faces[i].indices.size(); ++j)
            polygon->push_back(mesh.faces[i].indices[j]);
        // Добаляем грань к геометрии
        geom->addPrimitiveSet(polygon.get());
    }
    // Задаем массив нормалей
    geom->setNormalArray(mesh.normals.get());
    // Указываем OpenGL, что каждая нормаль применяется к примитиву
    geom->setNormalBinding(osg::Geometry::BIND_PER_PRIMITIVE_SET);
    // Создаем листовой узел графа сцены и добавляем в него сформированную нами геометрию
    osg::ref_ptr<osg::Geode> geode = new osg::Geode;
    geode->addDrawable(geom.get());
    // Возвращаем готовый листовой узелreturn geode.release();
}

At the end of the main.cpp file, call the REGISTER_OSGPLUGIN () macro

REGISTER_OSGPLUGIN( pmd, ReaderWriterPMD )

This macro forms an additional code that allows OSG, represented by the osgDB library, to construct an object of the type ReaderWriterPMD and call its methods to load files of the pmd type. Thus, the framework of the plugin is ready, the matter remains for small - to implement the loading and parsing of the pmd file.

7. Parsim 3D model file


Now all the functionality of the plug-in rests on the implementation of the parsePMD () method

pmd_mesh_t ReaderWriterPMD::parsePMD(std::istream &stream) const
{
    pmd_mesh_t mesh;
    // Читаем файл построчноwhile (!stream.eof())
    {
        // Получаем из файла очередную строкуstd::string line;
        std::getline(stream, line);
        // Разбиваем строку на составлящие - тип данный и параметрыstd::vector<std::string> tokens = parseLine(line);
        // Если тип данных - вершинаif (tokens[0] == "vertex")
        {
            // Читаем координаты вершины из списка параметров
            osg::Vec3 point;
            std::istringstreamiss(tokens[1]);
            iss >> point.x() >> point.y() >> point.z();
            // Добавляем вершину в массив вершин
            mesh.vertices->push_back(point);
        }
        // Если тип данных - граньif (tokens[0] == "face")
        {
            // Читаем все индексы вершин грани из списка параметровunsignedint idx = 0;
            std::istringstreamiss(tokens[1]);
            face_t face;
            while (!iss.eof())
            {
                iss >> idx;
                face.indices.push_back(idx);
            }
            // Добавляем грань в список граней
            mesh.faces.push_back(face);
            // Вычисляем нормаль к грани
            mesh.normals->push_back(mesh.calcFaceNormal(face));
        }
    }
    return mesh;
}

The parseLine () method parses the pmd file

std::vector<std::string> ReaderWriterPMD::parseLine(conststd::string &line) const
{
    std::vector<std::string> tokens;
    // Формируем временную строку, удаляя из текущей строки символ возврата каретки (для Windows)std::string tmp = delete_symbol(line, '\r');
    size_t pos = 0;
    std::string token;
    // Ищем разделитель типа данных и параметров, разбивая строку на два токена:// тип данных и сами данныеwhile ( (pos = tmp.find(':')) != std::string::npos )
    {
       // Выделяем токен типа данных (vertex или face в данном случае) 
       token = tmp.substr(0, pos);
       // Удаляем найденный токен из строки вместе с разделителем
       tmp.erase(0, pos + 1);
       if (!token.empty())
           tokens.push_back(token);
    }
    // Помещаем оставшуюся часть строки в список токенов
    tokens.push_back(tmp);
    return tokens;
}

This method will turn the string "vertex: 1.0 -1.0 0.0" into a list of two strings "vertex" and "1.0 -1.0 0.0". On the first line, we identify the data type — a vertex or a face; from the second, we extract the vertex coordinates data. For this method to work, you need the convenience function delete_symbol (), which deletes the specified character from the string and returns a string that does not contain this character.

std::stringdelete_symbol(conststd::string &str, char symbol){
    std::string tmp = str;
    tmp.erase(std::remove(tmp.begin(), tmp.end(), symbol), tmp.end());
    return tmp;
}

That is, now we have implemented all the functionality of our plugin and can test it.

8. Test the plugin


Compile the plugin and start debugging (F5). A debug version of the standard osgviewerd viewer will be launched, which analyzes the piramide.pmd file passed to it, loads our plugin and calls its readNode () method. If we did everything right, then we get this result.



It turns out that a quadrangular pyramid was hidden behind a list of vertices and faces in our invented 3D model file.

Why did we calculate the normals on our own? In one of the lessons we were offered the following method of automatic calculation of smoothed normals.

osgUtil::SmoothingVisitor::smooth(*geom);

Apply this function in our example, instead of assigning your own normals

//geom->setNormalArray(mesh.normals.get());//geom->setNormalBinding(osg::Geometry::BIND_PER_PRIMITIVE_SET);
osgUtil::SmoothingVisitor::smooth(*geom);

and we will get the following result. The



normals affect the calculation of the illumination of the model, and we see that in this situation, the smoothed normals lead to incorrect results of the calculation of the illumination of the pyramid. It is for this reason that we applied our bike to the calculation of normals. But I think that the explanation of the nuances of this is beyond the scope of this lesson.

Also popular now: