OpenSceneGraph: Sistema de plugins

imagem

1. Introdução


Em algum lugar das lições anteriores, já foi dito que o OSG suporta o carregamento de vários tipos de recursos, como imagens raster, modelos 3D de vários formatos ou, por exemplo, fontes através de seu próprio sistema de plug-in. O plug-in OSG é um componente separado que estende a funcionalidade do mecanismo e possui uma interface padronizada no OSG. O plug-in é implementado como uma biblioteca compartilhada dinâmica (DLL no Windows, Linux, etc.). Os nomes das bibliotecas de plug-ins correspondem a uma convenção específica

osgdb_< >.dll 

isto é, o nome do plug-in sempre contém o prefixo osgdb_. Uma extensão de arquivo informa ao mecanismo qual plug-in deve ser usado para baixar um arquivo com essa extensão. Por exemplo, quando escrevemos uma função no código

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

o mecanismo vê a extensão osg e carrega um plugin chamado osgdb_osg.dll (ou osgdb_osg.so no caso do Linux). O código do plug-in faz todo o trabalho sujo, retornando um ponteiro para um nó que descreve o modelo de cessna. Da mesma forma, tentando carregar uma imagem PNG

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

fará com que o plug-in osgdb_png.dll seja carregado, o que implementa um algoritmo para ler dados de uma imagem PNG e colocá-los em um objeto do tipo osg :: Image.

Todas as operações de trabalho com recursos externos são implementadas pelas funções da biblioteca osgDB, com as quais invariavelmente vinculamos os programas de exemplo para exemplo. Esta biblioteca conta com o sistema de plug-in OSG. Até o momento, o pacote OSG inclui muitos plug-ins que funcionam com a maioria dos formatos de imagem, modelos 3D e fontes usados ​​na prática. Os plug-ins fornecem dados de leitura (importação) de um formato específico e, na maioria dos casos, gravação de dados em um arquivo do formato necessário (exportação). O utilitário osgconv, em particular, permite converter dados de um formato para outro, por exemplo, o sistema de plug-in.

 $ osgconv cessna.osg cessna.3ds 

ele converte fácil e naturalmente o modelo cessna osg para o formato 3DS, que pode ser importado para um editor 3D, por exemplo, no Blender (a propósito, há uma extensão para trabalhar com o osg diretamente no Blender)



Há uma lista oficial de plug-ins OSG padrão com uma descrição de seu objetivo, mas é longo e tenho preguiça de trazê-lo aqui. É mais fácil observar o caminho da instalação da biblioteca na pasta bin / ospPlugins-xyz, em que x, y, z é o número da versão do OSG. A partir do nome do arquivo do plug-in, é fácil entender qual formato ele processa.

Se o OSG for compilado pelo compilador MinGW, um prefixo adicional mingw_ será adicionado ao nome padrão do plug-in, ou seja, o nome será semelhante a este

 mingw_osgdb_< >.dll 

A versão do plug-in compilado na configuração DEBUG está adicionalmente equipada com o sufixo d no final do nome, ou seja, o formato será

 osgdb_< >d.dll 

ou

 mingw_osgdb_< >d.dll 

ao montar o MinGW.

1. Pseudo-carregadores de plugins


Alguns plug-ins OSG atuam como pseudo-carregadores - isso significa que eles não estão vinculados a uma extensão de arquivo específica, mas adicionando um sufixo ao final do nome do arquivo, você pode especificar qual plug-in deve ser usado para baixar este arquivo, por exemplo

 $ osgviewer worldmap.shp.ogr 

Nesse caso, o nome real do arquivo no disco é worldmap.shp - esse arquivo armazena um mapa do mundo no formato de arquivo de forma ESRI. O sufixo .ogr diz à biblioteca osgDB para usar o plug-in osgdb_ogr para carregar este arquivo; caso contrário, o plugin osgdb_shp será usado.

Outro bom exemplo é o plug-in osgdb_ffmpeg. A biblioteca FFmpeg suporta mais de 100 codecs diferentes. Para ler qualquer um deles, podemos simplesmente adicionar o sufixo .ffmpeg após o nome do arquivo de mídia.

Além disso, alguns pseudo-carregadores nos permitem passar por um sufixo vários parâmetros que afetam o estado do objeto carregado, e já o encontramos em um exemplo com animação

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

A linha 0,90 indica para o plug-in osgdb_osg os parâmetros da orientação inicial do modelo carregado. Alguns pseudo-carregadores exigem parâmetros completamente específicos para funcionar.

2. API para desenvolver plugins de terceiros


É completamente lógico se, depois de toda a leitura, você tiver a ideia de que provavelmente não seria difícil escrever seu próprio plug-in para OSG, o que permitiria importar um formato não padrão de modelos ou imagens 3D. E este é um pensamento verdadeiro! O mecanismo de plug-in foi projetado apenas para expandir a funcionalidade do mecanismo sem alterar o próprio OSG. Para entender os princípios básicos de escrever um plugin, vamos tentar implementar um exemplo simples.

O desenvolvimento do plug-in é expandir a interface virtual de leitura / gravação fornecida pelo OSG. Essa funcionalidade é fornecida pela classe virtual osgDB :: ReaderWriter. Esta classe fornece vários métodos virtuais redefinidos pelo desenvolvedor do plugin.
MétodoDescrição do produto
supportExtensions ()Ele aceita dois parâmetros de string: extensão e descrição do arquivo. O método é sempre chamado no construtor da subclasse.
acceptExtension ()Retorna true se a extensão passada como argumento for suportada pelo plugin
fileExists ()Permite determinar se um determinado arquivo existe (o caminho é passado como parâmetro) no disco (retorna verdadeiro se for bem-sucedido)
readNode ()Aceita o nome do arquivo e as opções como um objeto osgDB :: Option. Funções para ler dados de um arquivo são implementadas pelo desenvolvedor
writeNode ()Aceita o nome do nó, o nome do arquivo desejado e as opções. As funções de gravação de dados no disco são implementadas pelo desenvolvedor
readImage ()Lendo dados de bitmap do disco
writeImage ()Gravando um bitmap no disco

A implementação do método readNode () pode ser descrita pelo seguinte código

 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; } 

É um pouco surpreendente que, em vez de um ponteiro para o nó do gráfico de cena, o método retorne o tipo osgDB :: ReaderWriter :: ReadResult. Esse tipo é um objeto de resultado de leitura e pode ser usado como um contêiner de nó, imagem, enumerador de estado (por exemplo, FILE_NOT_FOUND), outro objeto especial ou mesmo como uma sequência de mensagens de erro. Possui muitos construtores implícitos para implementar as funções descritas.

Outra classe útil é osgDB :: Options. Ele permite que você defina ou obtenha uma série de opções de carregamento usando os métodos setOptionString () e getOptionString (). Passar essa string para o construtor dessa classe como argumento também é permitido.

O desenvolvedor pode controlar o comportamento do plug-in definindo as configurações na sequência de parâmetros transmitida ao carregar o objeto, por exemplo, desta maneira

 //    osg::Node* node1 = osgDB::readNodeFile("cow.osg"); //     string osg::Node* node2 = osgDB::readNodeFile("cow.osg", new osgDB::Options(string)); 

3. Processamento de fluxo de dados no plugin OSG


A classe base osgDB :: ReaderWriter inclui um conjunto de métodos que processam os dados dos fluxos de entrada / saída fornecidos pela biblioteca C ++ padrão. A única diferença entre esses métodos de leitura / gravação e os discutidos acima é que, em vez do nome do arquivo, eles aceitam fluxos std :: istream & input ou std :: ostream & stream de saída. Usar um fluxo de E / S de arquivo é sempre preferível a usar um nome de arquivo. Para executar operações de leitura de arquivos, podemos usar o seguinte design de interface:

 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; } 

Após implementar o plug-in, podemos usar as funções padrão osgDB :: readNodeFile () e osgDB :: readImageFile () para carregar modelos e imagens, simplesmente especificando o caminho do arquivo. O OSG encontrará e fará o download do plug-in que escrevemos.

4. Nós escrevemos nosso próprio plugin



Portanto, ninguém nos incomoda em criar nosso próprio formato para armazenar dados em geometria tridimensional, e nós iremos fazê-lo

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 

Aqui, no início do arquivo, há uma lista de vértices com suas coordenadas. Os índices de vértices estão em ordem, começando do zero. Após a lista de vértices, aparece uma lista de faces. Cada face é definida por uma lista de índices de vértices a partir da qual é formada. Aparentemente, nada complicado. A tarefa é ler esse arquivo do disco e formar uma geometria tridimensional com base.

5. Configuração do projeto de plug-in: criar recursos de script


Se antes de criarmos aplicativos, agora precisamos escrever uma biblioteca dinâmica, e não apenas uma biblioteca, mas um plug-in OSG que atenda a certos requisitos. Começaremos a cumprir esses requisitos com um script de construção do projeto que será parecido com este

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) 

Analisaremos as nuances individuais com mais detalhes

 TEMPLATE = lib 

significa que vamos construir a biblioteca. Para impedir a geração de links simbólicos com a ajuda de quais problemas dos conflitos de versão da biblioteca são resolvidos nos sistemas * nix, indicamos ao sistema de compilação que essa biblioteca será um plug-in, ou seja, será carregada na memória "on the fly"

 CONFIG += plugin 

Em seguida, excluímos a geração do prefixo da lib, que é adicionado ao usar os compiladores da família gcc e é levado em consideração pelo ambiente de tempo de execução ao carregar a biblioteca

 CONFIG += no_plugin_name_prefix 

Defina o nome do arquivo da biblioteca

 TARGET = osgdb_pmd 

onde pmd é a extensão de arquivo do formato de modelo 3D que inventamos. Além disso, devemos indicar que, no caso da montagem MinGW, o prefixo mingw_

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

Especifique o caminho de construção da biblioteca: para Windows

 DESTDIR = $$(OSG_PLUGINS_PATH) 

para linux

 DESTDIR = /usr/lib/osgPlugins-3.7.0 

Para o Linux, com essa indicação do caminho (que sem dúvida é uma muleta, mas ainda não encontrei outra solução), damos o direito de gravar na pasta especificada com plugins OSG de um usuário comum

 # chmod 666 /usr/lib/osgPlugins-3.7.0 

Todas as outras configurações de construção são semelhantes às usadas na montagem de aplicativos de amostra anteriormente.

6. Configuração do projeto de plug-in: recursos do modo de depuração


Como este projeto é uma biblioteca dinâmica, deve haver um programa que carrega essa biblioteca no processo de sua execução. Pode ser qualquer aplicativo que use OSG e no qual a função será chamada

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

Nesse caso, nosso plugin será carregado. Para não escrever esse programa por conta própria, usaremos uma solução pronta - o visualizador padrão do osgviewer incluído no pacote de entrega do mecanismo. Se no console, execute

 $ osgviewer piramide.pmd 

então também acionará o plugin. Nas configurações de inicialização do projeto, especifique o caminho para osgviewerd, como o diretório de trabalho, especifique o diretório em que o arquivo piramide.pmd está localizado e especifique o mesmo arquivo nas opções da linha de comandos do osgviewer



Agora podemos executar o plug-in e depurá-lo diretamente do QtCreator IDE.

6. Implementamos a estrutura do plugin


Este exemplo generaliza em certa medida o conhecimento que já recebemos sobre OSG de lições anteriores. Ao escrever um plugin, temos que

  1. Selecione uma estrutura de dados para armazenar as informações da geometria do modelo lidas em um arquivo de modelo
  2. Leia e analise (analise) o arquivo de dados do modelo
  3. Configure corretamente o objeto geométrico osg :: Drawable com base nos dados lidos no arquivo
  4. Construir um subgráfico de cena para um modelo carregado

Então, por tradição, darei todo o código fonte do plugin

Plug-in 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> //------------------------------------------------------------------------------ // //------------------------------------------------------------------------------ struct face_t { std::vector<unsigned int> indices; }; //------------------------------------------------------------------------------ // //------------------------------------------------------------------------------ struct pmd_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(const face_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()); } }; //------------------------------------------------------------------------------ // //------------------------------------------------------------------------------ class ReaderWriterPMD : public osgDB::ReaderWriter { public: ReaderWriterPMD(); virtual ReadResult readNode(const std::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(const std::string &line) const; }; #endif 

main.cpp

 #include "main.h" //------------------------------------------------------------------------------ // //------------------------------------------------------------------------------ ReaderWriterPMD::ReaderWriterPMD() { supportsExtension("pmd", "PMD model file"); } //------------------------------------------------------------------------------ // //------------------------------------------------------------------------------ osgDB::ReaderWriter::ReadResult ReaderWriterPMD::readNode( const std::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::istringstream iss(tokens[1]); iss >> point.x() >> point.y() >> point.z(); mesh.vertices->push_back(point); } if (tokens[0] == "face") { unsigned int idx = 0; std::istringstream iss(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::string delete_symbol(const std::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(const std::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 ) 


Primeiro, vamos cuidar das estruturas para armazenar dados de geometria.

 struct face_t { std::vector<unsigned int> indices; }; 

- descreve a face definida pela lista de índices dos vértices pertencentes a essa face. O modelo como um todo será descrito por essa estrutura

 struct pmd_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(const face_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()); } }; 

A estrutura consiste em variáveis-membro para armazenar dados: vértices - para armazenar uma matriz de vértices de um objeto geométrico; normais - uma matriz de normais para as faces do objeto; faces - uma lista de faces do objeto. O construtor de estrutura inicializa imediatamente ponteiros inteligentes

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

Além disso, a estrutura contém um método que permite calcular o vetor normal para a face calcFaceNormal () como um parâmetro que utiliza uma estrutura que descreve a face. Ainda não entraremos em detalhes da implementação desse método, mas os analisaremos um pouco mais tarde.

Assim, decidimos sobre as estruturas nas quais armazenaremos os dados da geometria. Agora vamos escrever a estrutura do nosso plugin, a saber, implementamos a classe herdada osgDB :: ReaderWriter

 class ReaderWriterPMD : public osgDB::ReaderWriter { public: ReaderWriterPMD(); virtual ReadResult readNode(const std::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(const std::string &line) const; }; 

Conforme recomendado na descrição da API para o desenvolvimento de plug-ins, nesta classe redefinimos os métodos de leitura de dados de um arquivo e os convertemos em um subgrafo da cena. O método readNode () realiza duas sobrecargas - uma aceita o nome do arquivo como entrada e a outra recebe a entrada padrão. O construtor da classe define as extensões de arquivo suportadas pelo plug-in

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

A primeira sobrecarga do método readNode () analisa a correção do nome e do caminho do arquivo, associa um fluxo de entrada padrão ao arquivo e chama a segunda sobrecarga, que faz o trabalho principal

 osgDB::ReaderWriter::ReadResult ReaderWriterPMD::readNode( const std::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); } 

Na segunda sobrecarga, implementamos o algoritmo de geração de objetos para 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(); } 

No final do arquivo main.cpp, chame a macro REGISTER_OSGPLUGIN ().

 REGISTER_OSGPLUGIN( pmd, ReaderWriterPMD ) 

Essa macro gera código adicional que permite ao OSG, na forma da biblioteca osgDB, construir um objeto do tipo ReaderWriterPMD e chamar seus métodos para carregar arquivos do tipo pmd. Assim, a estrutura do plugin está pronta, o que resta é pequeno - para implementar o carregamento e a análise do arquivo pmd.

7. Arquivo de modelo 3D Parsim


Agora toda a funcionalidade do plugin se baseia na implementação do método parsePMD ()

 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::istringstream iss(tokens[1]); iss >> point.x() >> point.y() >> point.z(); //      mesh.vertices->push_back(point); } //    -  if (tokens[0] == "face") { //         unsigned int idx = 0; std::istringstream iss(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; } 

O método ParseLine () analisa a linha do arquivo pmd

 std::vector<std::string> ReaderWriterPMD::parseLine(const std::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; } 

Este método transformará a cadeia "vértice: 1,0 -1,0 0,0" em uma lista das duas linhas "vértice" e "1,0 -1,0 0,0". Na primeira linha, identificamos o tipo de dados - o vértice ou a face, a partir da segunda extraímos os dados nas coordenadas do vértice. Para garantir a operação desse método, precisamos da função auxiliar delete_symbol (), que remove o caractere fornecido da string e retorna uma string que não contém esse caractere

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

Ou seja, agora implementamos todas as funcionalidades do nosso plugin e podemos testá-lo.

8. Testando o Plug-in


Compilamos o plug-in e executamos a depuração (F5). Será lançada uma versão de depuração do visualizador osgviewerd padrão, que analisará o arquivo piramide.pmd passado para ele, carregue nosso plug-in e chame seu método readNode (). Se fizermos tudo certo, obteremos esse resultado:



a lista de vértices e faces em nosso arquivo inventado do modelo 3D escondeu uma pirâmide quadrangular.

Por que nós mesmos calculamos as normais? Em uma das lições, nos foi oferecido o seguinte método de cálculo automático de normais suavizadas

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

Aplicamos essa função em nosso exemplo, em vez de atribuir nossos próprios normais

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

e obtemos o seguinte resultado: As



normais afetam o cálculo da iluminação do modelo e vemos que, nessa situação, as normais suavizadas levam a resultados incorretos do cálculo da iluminação da pirâmide. É por esse motivo que aplicamos nossa bicicleta no cálculo das normais. Mas acho que explicar as nuances disso está além do escopo desta lição.

Source: https://habr.com/ru/post/pt438296/


All Articles