OpenSceneGraph: Sistema de complementos

imagen

Introduccion


En alguna parte de las lecciones anteriores, ya se dijo que OSG admite la carga de diversos tipos de recursos, como imágenes ráster, modelos 3D de varios formatos o, por ejemplo, fuentes a través de su propio sistema de complemento. El complemento OSG es un componente separado que extiende la funcionalidad del motor y tiene una interfaz estandarizada dentro del OSG. El complemento se implementa como una biblioteca dinámica compartida (dll en Windows, Linux, etc.). Los nombres de las bibliotecas de complementos corresponden a una convención específica

osgdb_< >.dll 

es decir, el nombre del complemento siempre contiene el prefijo osgdb_. Una extensión de archivo le dice al motor qué complemento se debe usar para descargar un archivo con esta extensión. Por ejemplo, cuando escribimos una función en código

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

el motor ve la extensión osg y carga un complemento llamado osgdb_osg.dll (u osgdb_osg.so en el caso de Linux). El código del complemento hace todo el trabajo sucio devolviéndonos un puntero a un nodo que describe el modelo cessna. Del mismo modo, tratando de cargar una imagen PNG

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

hará que se cargue el complemento osgdb_png.dll, que implementa un algoritmo para leer datos de una imagen PNG y colocar estos datos en un objeto de tipo osg :: Image.

Todas las operaciones para trabajar con recursos externos se implementan mediante las funciones de la biblioteca osgDB, con la que invariablemente vinculamos los programas de un ejemplo a otro. Esta biblioteca se basa en el sistema de complemento OSG. Hasta la fecha, el paquete OSG incluye muchos complementos que funcionan con la mayoría de los formatos de imagen, modelos 3D y fuentes utilizados en la práctica. Los complementos proporcionan datos de lectura (importación) de un formato específico y, en la mayoría de los casos, escritura de datos en un archivo del formato requerido (exportación). La utilidad osgconv, en particular, le permite convertir datos de un formato a otro, por ejemplo, el sistema de complemento.

 $ osgconv cessna.osg cessna.3ds 

convierte fácil y naturalmente el modelo cessna osg en formato 3DS, que luego puede importarse a un editor 3D, por ejemplo, a Blender (por cierto, hay una extensión para trabajar con osg directamente para Blender)



Hay una lista oficial de complementos OSG estándar con una descripción de su propósito, pero es larga y soy demasiado vaga para traerla aquí. Es más fácil mirar la ruta de instalación de la biblioteca en la carpeta bin / ospPlugins-xyz, donde x, y, z es el número de versión de OSG. Desde el nombre del archivo de complemento, es fácil entender qué formato procesa.

Si el compilador MinGW compila el OSG, se agrega un prefijo adicional mingw_ al nombre estándar del complemento, es decir, el nombre se verá así

 mingw_osgdb_< >.dll 

La versión del complemento compilado en la configuración DEBUG también está equipada con el sufijo d al final del nombre, es decir, el formato será

 osgdb_< >d.dll 

o

 mingw_osgdb_< >d.dll 

cuando se ensambla MinGW.

1. Complementos pseudo-cargadores


Algunos complementos OSG realizan las funciones de los llamados pseudocargadores: esto significa que no están vinculados a una extensión de archivo específica, pero al agregar un sufijo al final del nombre del archivo, puede especificar qué complemento se debe usar para descargar este archivo, por ejemplo

 $ osgviewer worldmap.shp.ogr 

En este caso, el nombre real del archivo en el disco es worldmap.shp: este archivo almacena un mapa mundial en formato de archivo ESRI. El sufijo .ogr le dice a la biblioteca osgDB que use el complemento osgdb_ogr para cargar este archivo; de lo contrario, se usará el complemento osgdb_shp.

Otro buen ejemplo es el complemento osgdb_ffmpeg. La biblioteca FFmpeg admite más de 100 códecs diferentes. Para leer cualquiera de ellos, simplemente podemos agregar el sufijo .ffmpeg después del nombre del archivo multimedia.

Además de esto, algunos pseudocargadores nos permiten pasar a través de un sufijo una serie de parámetros que afectan el estado del objeto cargado, y ya lo encontramos en un ejemplo con animación

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

La línea 0.90 indica al complemento osgdb_osg los parámetros de la orientación inicial del modelo cargado. Algunos pseudo-cargadores requieren parámetros completamente específicos para funcionar.

2. API para desarrollar complementos de terceros


Es completamente lógico si, después de toda la lectura, tuvo la idea de que probablemente no sería difícil escribir su propio complemento para OSG, lo que le permitiría importar un formato no estándar de imágenes o modelos 3D. Y este es un pensamiento verdadero! El mecanismo del complemento está diseñado para expandir la funcionalidad del motor sin cambiar el OSG. Para comprender los principios básicos de escribir un complemento, intentemos implementar un ejemplo simple.

El desarrollo del complemento consiste en expandir la interfaz virtual de lectura / escritura proporcionada por OSG. Esta funcionalidad es proporcionada por la clase virtual osgDB :: ReaderWriter. Esta clase proporciona una serie de métodos virtuales redefinidos por el desarrollador del complemento.
MétodoDescripción
supportsExtensions ()Acepta dos parámetros de cadena: extensión de archivo y descripción. El método siempre se llama en el constructor de la subclase.
acceptExtension ()Devuelve verdadero si la extensión pasada como argumento es compatible con el complemento
fileExists ()Le permite determinar si existe un archivo determinado (la ruta se pasa como un parámetro) en el disco (devuelve verdadero si tiene éxito)
readNode ()Acepta el nombre del archivo y las opciones como un objeto osgDB :: Option. El desarrollador implementa las funciones para leer datos de un archivo
writeNode ()Acepta el nombre del nodo, el nombre del archivo deseado y las opciones. El desarrollador implementa las funciones de escritura de datos en el disco
readImage ()Lectura de datos de mapa de bits del disco
writeImage ()Escribir un mapa de bits en el disco

La implementación del método readNode () se puede describir mediante el siguiente 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; } 

Es un poco sorprendente que, en lugar de un puntero al nodo del gráfico de escena, el método devuelva el tipo osgDB :: ReaderWriter :: ReadResult. Este tipo es un objeto de resultado de lectura y se puede usar como contenedor de nodo, imagen, enumerador de estado (por ejemplo, FILE_NOT_FOUND), otro objeto especial o incluso como una cadena de mensaje de error. Tiene muchos constructores implícitos para implementar las funciones descritas.

Otra clase útil es osgDB :: Options. Puede permitirle establecer u obtener una serie de opciones de carga utilizando los métodos setOptionString () y getOptionString (). También se permite pasar esta cadena al constructor de esta clase como argumento.

El desarrollador puede controlar el comportamiento del complemento configurando la configuración en la cadena de parámetros que se pasa al cargar el objeto, por ejemplo, de esta manera

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

3. Procesamiento del flujo de datos en el complemento OSG


La clase base osgDB :: ReaderWriter incluye un conjunto de métodos que procesan los datos de los flujos de entrada / salida proporcionados por la biblioteca estándar de C ++. La única diferencia entre estos métodos de lectura / escritura y los discutidos anteriormente es que, en lugar del nombre del archivo, aceptan std :: istream & input streams o std :: ostream & output stream. Usar un flujo de E / S de archivo siempre es preferible a usar un nombre de archivo. Para realizar operaciones de lectura de archivos, podemos usar el siguiente diseño de interfaz:

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

Después de implementar el complemento, podemos usar las funciones estándar osgDB :: readNodeFile () y osgDB :: readImageFile () para cargar modelos e imágenes, simplemente especificando la ruta del archivo. OSG encontrará y descargará el complemento que escribimos.

4. Escribimos nuestro propio complemento



Por lo tanto, nadie nos molesta en crear nuestro propio formato para almacenar datos en geometría tridimensional, y lo inventaremos.

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 

Aquí al principio del archivo hay una lista de vértices con sus coordenadas. Los índices de vértices van en orden, comenzando desde cero. Después de la lista de vértices viene una lista de caras. Cada cara está definida por una lista de índices de vértices a partir de los cuales se forma. Al parecer, nada complicado. La tarea es leer este archivo desde el disco y formar una geometría tridimensional sobre su base.

5. Configuración del proyecto del complemento: características del script de compilación


Si antes creábamos aplicaciones, ahora tenemos que escribir una biblioteca dinámica, y no solo una biblioteca, sino un complemento OSG que satisfaga ciertos requisitos. Comenzaremos a cumplir estos requisitos con un script de compilación del proyecto que se verá así

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) 

Analizaremos los matices individuales con más detalle.

 TEMPLATE = lib 

significa que construiremos la biblioteca. Para evitar la generación de enlaces simbólicos con la ayuda de qué problemas de conflictos de versiones de la biblioteca se resuelven en los sistemas * nix, le indicamos al sistema de compilación que esta biblioteca será un complemento, es decir, se cargará en la memoria "sobre la marcha"

 CONFIG += plugin 

A continuación, excluimos la generación del prefijo lib, que se agrega al usar los compiladores de la familia gcc y que se tiene en cuenta en el entorno de tiempo de ejecución al cargar la biblioteca

 CONFIG += no_plugin_name_prefix 

Establecer el nombre del archivo de biblioteca

 TARGET = osgdb_pmd 

donde pmd es la extensión de archivo del formato del modelo 3D que inventamos. Además, debemos indicar que en el caso del ensamblaje MinGW, el prefijo mingw_ se agrega necesariamente al nombre

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

Especifique la ruta de compilación de la biblioteca: para Windows

 DESTDIR = $$(OSG_PLUGINS_PATH) 

para linux

 DESTDIR = /usr/lib/osgPlugins-3.7.0 

Para Linux, con tal indicación de la ruta (que sin duda es una muleta, pero aún no he encontrado otra solución), le damos el derecho de escribir en la carpeta especificada con complementos OSG de un usuario ordinario

 # chmod 666 /usr/lib/osgPlugins-3.7.0 

Todas las demás configuraciones de compilación son similares a las utilizadas en el ensamblaje de aplicaciones de muestra anteriores.

6. Configuración del proyecto del complemento: características del modo de depuración


Dado que este proyecto es una biblioteca dinámica, debe haber un programa que cargue esta biblioteca en el proceso de su ejecución. Puede ser cualquier aplicación que use OSG y en la que se llamará a la función

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

En este caso, nuestro complemento se cargará. Para no escribir un programa de este tipo, usaremos una solución preparada: el visor estándar de osgviewer, que se incluye en el paquete de entrega del motor. Si en la consola ejecuta

 $ osgviewer piramide.pmd 

entonces también activará el complemento. En la configuración de inicio del proyecto, especifique la ruta a osgviewerd, como el directorio de trabajo, especifique el directorio donde se encuentra el archivo piramide.pmd y especifique el mismo archivo en las opciones de la línea de comandos de osgviewer



Ahora podemos ejecutar el complemento y depurarlo directamente desde QtCreator IDE.

6. Implementamos el marco del complemento


Este ejemplo, en cierta medida, generaliza el conocimiento que ya hemos recibido sobre OSG de lecciones anteriores. Al escribir un complemento, tenemos que

  1. Seleccione una estructura de datos para almacenar la información de geometría del modelo leída de un archivo de modelo
  2. Leer y analizar (analizar) el archivo de datos del modelo
  3. Configure correctamente el objeto geométrico osg :: Drawable basado en los datos leídos del archivo
  4. Construye un subgrafo de escena para un modelo cargado

Entonces, por tradición, daré el código fuente completo del complemento

Complemento 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 ) 


Primero, cuidemos las estructuras para almacenar datos de geometría.

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

- describe la cara definida por la lista de índices de los vértices que pertenecen a esta cara. El modelo como un todo será descrito por dicha estructura.

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

La estructura consta de variables miembro para almacenar datos: vértices: para almacenar una matriz de vértices de un objeto geométrico; normales: una serie de normales a las caras del objeto; caras: una lista de caras del objeto. El constructor de estructura inicializa inmediatamente punteros inteligentes

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

Además, la estructura contiene un método que le permite calcular el vector normal de la cara calcFaceNormal () como un parámetro que toma una estructura que describe la cara. Todavía no entraremos en detalles sobre la implementación de este método, los analizaremos un poco más tarde.

Por lo tanto, decidimos sobre las estructuras en las que almacenaremos los datos de geometría. Ahora escribamos el marco de nuestro complemento, es decir, implementamos la clase de herencia 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; }; 

Como se recomienda en la descripción de la API para desarrollar complementos, en esta clase redefinimos los métodos para leer datos de un archivo y convertirlos en un subgrafo de la escena. El método readNode () realiza dos sobrecargas: una acepta el nombre del archivo como entrada y la otra recibe una secuencia de entrada estándar. El constructor de la clase define las extensiones de archivo compatibles con el complemento.

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

La primera sobrecarga del método readNode () analiza la exactitud del nombre y la ruta del archivo, asocia una secuencia de entrada estándar con el archivo y llama a la segunda sobrecarga, que hace el trabajo 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); } 

En la segunda sobrecarga, implementamos el algoritmo de generación 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(); } 

Al final del archivo main.cpp, llame a la macro REGISTER_OSGPLUGIN ().

 REGISTER_OSGPLUGIN( pmd, ReaderWriterPMD ) 

Esta macro genera código adicional que permite a OSG, en forma de biblioteca osgDB, construir un objeto de tipo ReaderWriterPMD y llamar a sus métodos para cargar archivos de tipo pmd. Por lo tanto, el marco del complemento está listo, la cosa queda por pequeña: implementar la carga y el análisis del archivo pmd.

7. archivo de modelo 3D Parsim


Ahora toda la funcionalidad del complemento se basa en la implementación del 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; } 

El método ParseLine () analiza la línea del archivo 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 convertirá la cadena "vértice: 1.0 -1.0 0.0" en una lista de las dos líneas "vértice" y "1.0 -1.0 0.0". En la primera línea identificamos el tipo de datos: el vértice o la cara, a partir de la segunda extraemos los datos en las coordenadas del vértice. Para garantizar el funcionamiento de este método, necesitamos la función auxiliar delete_symbol (), que elimina el carácter dado de la cadena y devuelve una cadena que no contiene este carácter

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

Es decir, ahora hemos implementado toda la funcionalidad de nuestro complemento y podemos probarlo.

8. Probar el complemento


Compilamos el complemento y ejecutamos la depuración (F5). Se lanzará una versión de depuración del visor estándar osgviewerd, que analizará el archivo piramide.pmd que se le pasó, cargará nuestro complemento y llamará a su método readNode (). Si hicimos todo bien, obtendremos ese resultado.



Resulta que la lista de vértices y caras en nuestro archivo inventado del modelo 3D ocultaba una pirámide cuadrangular.

¿Por qué calculamos las normales nosotros mismos? En una de las lecciones, se nos ofreció el siguiente método de cálculo automático de normales suavizadas

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

Aplicamos esta función en nuestro ejemplo, en lugar de asignar nuestras propias normales

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

y obtenemos el siguiente resultado: las



normales afectan el cálculo de la iluminación del modelo, y vemos que en esta situación las normales suavizadas conducen a resultados incorrectos del cálculo de la iluminación de la pirámide. Es por esta razón que aplicamos nuestra bicicleta al cálculo de las normales. Pero creo que explicar los matices de esto está más allá del alcance de esta lección.

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


All Articles