OpenSceneGraph: Plugin-System

Bild

Einführung


In früheren Lektionen wurde bereits gesagt, dass OSG das Laden verschiedener Arten von Ressourcen unterstützt, z. B. Rasterbilder, 3D-Modelle in verschiedenen Formaten oder beispielsweise Schriftarten über ein eigenes Plug-In-System. Das OSG-Plugin ist eine separate Komponente, die die Funktionalität der Engine erweitert und über eine innerhalb des OSG standardisierte Schnittstelle verfügt. Das Plugin ist als dynamische gemeinsam genutzte Bibliothek implementiert (DLL unter Windows, also unter Linux usw.). Die Namen der Plugin-Bibliotheken entsprechen einer bestimmten Konvention

osgdb_< >.dll 

Das heißt, der Name des Plugins enthält immer das Präfix osgdb_. Eine Dateierweiterung teilt der Engine mit, welches Plug-In zum Herunterladen einer Datei mit dieser Erweiterung verwendet werden soll. Zum Beispiel, wenn wir eine Funktion in Code schreiben

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

Die Engine sieht die Erweiterung osg und lädt ein Plugin mit dem Namen osgdb_osg.dll (oder osgdb_osg.so bei Linux). Der Plugin-Code erledigt die ganze Drecksarbeit, indem er uns einen Zeiger auf einen Knoten zurückgibt, der das Cessna-Modell beschreibt. Ebenso wird versucht, ein PNG-Bild zu laden

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

Dadurch wird das Plugin osgdb_png.dll geladen, das einen Algorithmus zum Lesen von Daten aus einem PNG-Bild und zum Platzieren dieser Daten in einem Objekt vom Typ osg :: Image implementiert.

Alle Operationen zur Arbeit mit externen Ressourcen werden durch die Funktionen der osgDB-Bibliothek implementiert, mit denen wir die Programme immer von Beispiel zu Beispiel verknüpfen. Diese Bibliothek basiert auf dem OSG-Plugin-System. Bisher enthält das OSG-Paket viele Plug-Ins, die mit den meisten in der Praxis verwendeten Bildformaten, 3D-Modellen und Schriftarten funktionieren. Plugins bieten sowohl das Lesen von Daten (Importieren) eines bestimmten Formats als auch das Schreiben von Daten in eine Datei des erforderlichen Formats (Exportieren). Insbesondere mit dem Dienstprogramm osgconv können Sie Daten von einem Format in ein anderes konvertieren, z. B. das Plug-In-System.

 $ osgconv cessna.osg cessna.3ds 

Es konvertiert das cessna osg-Modell einfach und natürlich in das 3DS-Format, das dann in einen 3D-Editor importiert werden kann, z. B. in Blender (es gibt übrigens eine Erweiterung für die direkte Arbeit mit osg für Blender).



Es gibt eine offizielle Liste von Standard-OSG-Plugins mit einer Beschreibung ihres Zwecks, aber sie ist lang und ich bin zu faul, um sie hierher zu bringen. Es ist einfacher, den Installationspfad der Bibliothek im Ordner bin / ospPlugins-xyz anzuzeigen, wobei x, y, z die OSG-Versionsnummer ist. Aus dem Namen der Plugin-Datei ist leicht ersichtlich, welches Format sie verarbeitet.

Wenn das OSG vom MinGW-Compiler kompiliert wird, wird dem Standardnamen des Plugins ein zusätzliches Präfix mingw_ hinzugefügt, dh der Name sieht folgendermaßen aus

 mingw_osgdb_< >.dll 

Die in der DEBUG-Konfiguration kompilierte Version des Plugins ist zusätzlich mit dem Suffix d am Ende des Namens ausgestattet, dh das Format wird sein

 osgdb_< >d.dll 

oder

 mingw_osgdb_< >d.dll 

beim Zusammenbau von MinGW.

1. Plugins Pseudo-Lader


Einige OSG-Plugins erfüllen die Funktionen sogenannter Pseudo-Loader. Dies bedeutet, dass sie nicht an eine bestimmte Dateierweiterung gebunden sind. Durch Hinzufügen eines Suffixes am Ende des Dateinamens können Sie beispielsweise angeben, welches Plug-In zum Herunterladen dieser Datei verwendet werden soll

 $ osgviewer worldmap.shp.ogr 

In diesem Fall lautet der tatsächliche Name der Datei auf der Festplatte worldmap.shp. In dieser Datei wird eine Weltkarte im ESRI-Shapefile-Format gespeichert. Das Suffix .ogr weist die osgDB-Bibliothek an, das Plugin osgdb_ogr zum Laden dieser Datei zu verwenden. Andernfalls wird das Plugin osgdb_shp verwendet.

Ein weiteres gutes Beispiel ist das Plugin osgdb_ffmpeg. Die FFmpeg-Bibliothek unterstützt über 100 verschiedene Codecs. Um eines davon zu lesen, können wir einfach das Suffix .ffmpeg nach dem Namen der Mediendatei hinzufügen.

Darüber hinaus ermöglichen uns einige Pseudo-Lader, eine Reihe von Parametern durch ein Suffix zu übergeben, die sich auf den Status des geladenen Objekts auswirken. Dies ist uns bereits in einem Beispiel mit Animation begegnet

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

Die Zeile 0.90 zeigt dem osgdb_osg-Plugin die anfänglichen Orientierungsparameter des geladenen Modells an. Einige Pseudo-Lader benötigen zum Arbeiten vollständig spezifische Parameter.

2. API zum Entwickeln von Plugins von Drittanbietern


Es ist völlig logisch, wenn Sie nach all dem Lesen die Idee hatten, dass es wahrscheinlich nicht schwierig sein würde, ein eigenes Plug-In für OSG zu schreiben, mit dem Sie ein nicht standardmäßiges Format von 3D-Modellen oder Bildern importieren können. Und das ist ein wahrer Gedanke! Der Plugin-Mechanismus wurde nur entwickelt, um die Funktionalität der Engine zu erweitern, ohne das OSG selbst zu ändern. Um die Grundprinzipien des Schreibens eines Plugins zu verstehen, versuchen wir, ein einfaches Beispiel zu implementieren.

Die Entwicklung des Plugins besteht darin, die von OSG bereitgestellte virtuelle Lese- / Schreibschnittstelle zu erweitern. Diese Funktionalität wird von der virtuellen Klasse osgDB :: ReaderWriter bereitgestellt. Diese Klasse bietet eine Reihe von virtuellen Methoden, die vom Plugin-Entwickler neu definiert wurden.
MethodeBeschreibung
unterstütztExtensions ()Es werden zwei Zeichenfolgenparameter akzeptiert: Dateierweiterung und Beschreibung. Die Methode wird immer im Konstruktor der Unterklasse aufgerufen.
akzeptiertExtension ()Gibt true zurück, wenn die als Argument übergebene Erweiterung vom Plugin unterstützt wird
fileExists ()Hier können Sie feststellen, ob eine bestimmte Datei auf der Festplatte vorhanden ist (der Pfad wird als Parameter übergeben) (gibt bei Erfolg true zurück).
readNode ()Akzeptiert den Dateinamen und die Optionen als osgDB :: Option-Objekt. Funktionen zum Lesen von Daten aus einer Datei werden vom Entwickler implementiert
writeNode ()Akzeptiert den Knotennamen, den gewünschten Dateinamen und die Optionen. Die Funktionen zum Schreiben von Daten auf die Festplatte werden vom Entwickler implementiert
readImage ()Lesen von Festplatten-Bitmap-Daten
writeImage ()Schreiben einer Bitmap auf die Festplatte

Die Implementierung der Methode readNode () kann durch den folgenden Code beschrieben werden

 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 ist ein wenig überraschend, dass die Methode anstelle eines Zeigers auf den Knoten des Szenendiagramms den Typ osgDB :: ReaderWriter :: ReadResult zurückgibt. Dieser Typ ist ein Leseergebnisobjekt und kann als Knotencontainer, Bild, Statusaufzähler (z. B. FILE_NOT_FOUND), ein anderes spezielles Objekt oder sogar als Fehlermeldung verwendet werden. Es gibt viele implizite Konstruktoren zum Implementieren der beschriebenen Funktionen.

Eine weitere nützliche Klasse ist osgDB :: Options. Mit den Methoden setOptionString () und getOptionString () können Sie eine Reihe von Ladeoptionen festlegen oder abrufen. Das Übergeben dieser Zeichenfolge an den Konstruktor dieser Klasse als Argument ist ebenfalls zulässig.

Der Entwickler kann das Verhalten des Plugins steuern, indem er die Einstellungen in der Parameterzeichenfolge festlegt, die beispielsweise beim Laden des Objekts übergeben wird

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

3. Datenflussverarbeitung im OSG-Plugin


Die Basisklasse osgDB :: ReaderWriter enthält eine Reihe von Methoden, die die Daten von Eingabe- / Ausgabestreams verarbeiten, die von der Standard-C ++ - Bibliothek bereitgestellt werden. Der einzige Unterschied zwischen diesen Lese- / Schreibmethoden und den oben beschriebenen besteht darin, dass sie anstelle des Dateinamens std :: istream & Eingabestreams oder std :: ostream & Ausgabestream akzeptieren. Die Verwendung eines Datei-E / A-Streams ist immer der Verwendung eines Dateinamens vorzuziehen. Um Dateilesevorgänge auszuführen, können wir das folgende Schnittstellendesign verwenden:

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

Nach der Implementierung des Plugins können wir die Standardfunktionen osgDB :: readNodeFile () und osgDB :: readImageFile () verwenden, um Modelle und Bilder zu laden, indem wir einfach den Dateipfad angeben. OSG findet das von uns geschriebene Plugin und lädt es herunter.

4. Wir schreiben unser eigenes Plugin



Daher stört uns niemand daran, ein eigenes Format zum Speichern von Daten zur dreidimensionalen Geometrie zu entwickeln, und wir werden es uns einfallen lassen

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 

Hier am Anfang der Datei befindet sich eine Liste der Eckpunkte mit ihren Koordinaten. Scheitelpunktindizes gehen in der Reihenfolge von Null aus. Nach der Liste der Eckpunkte folgt eine Liste der Gesichter. Jede Fläche wird durch eine Liste von Scheitelpunktindizes definiert, aus denen sie gebildet wird. Anscheinend nichts kompliziertes. Die Aufgabe besteht darin, diese Datei von der Festplatte zu lesen und auf ihrer Basis eine dreidimensionale Geometrie zu bilden.

5. Plugin-Projekt-Setup: Erstellen Sie Skriptfunktionen


Wenn wir zuvor Anwendungen erstellt haben, müssen wir jetzt eine dynamische Bibliothek schreiben, und zwar nicht nur eine Bibliothek, sondern ein OSG-Plug-In, das bestimmte Anforderungen erfüllt. Wir werden beginnen, diese Anforderungen mit einem Projekterstellungsskript zu erfüllen, das so aussieht

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) 

Wir werden einzelne Nuancen genauer analysieren

 TEMPLATE = lib 

bedeutet, wir werden die Bibliothek bauen. Um die Erzeugung symbolischer Verknüpfungen zu verhindern, mit deren Hilfe Probleme mit Bibliotheksversionskonflikten in * nix-Systemen gelöst werden, weisen wir das Build-System darauf hin, dass diese Bibliothek ein Plug-In ist, dh "on the fly" in den Speicher geladen wird.

 CONFIG += plugin 

Als nächstes schließen wir die Generierung des lib-Präfixes aus, das bei Verwendung der Compiler der gcc-Familie hinzugefügt wird und von der Laufzeitumgebung beim Laden der Bibliothek berücksichtigt wird

 CONFIG += no_plugin_name_prefix 

Legen Sie den Namen der Bibliotheksdatei fest

 TARGET = osgdb_pmd 

Dabei ist pmd die Dateierweiterung des von uns erfundenen 3D-Modellformats. Ferner müssen wir angeben, dass im Fall einer MinGW-Assembly das Präfix mingw_ notwendigerweise zum Namen hinzugefügt wird

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

Geben Sie den Erstellungspfad der Bibliothek an: für Windows

 DESTDIR = $$(OSG_PLUGINS_PATH) 

für Linux

 DESTDIR = /usr/lib/osgPlugins-3.7.0 

Für Linux geben wir mit einer solchen Angabe des Pfades (der zweifellos eine Krücke ist, aber ich habe noch keine andere Lösung gefunden) das Recht, mit OSG-Plugins von einem normalen Benutzer in den angegebenen Ordner zu schreiben

 # chmod 666 /usr/lib/osgPlugins-3.7.0 

Alle anderen Build-Einstellungen ähneln denen, die zuvor beim Zusammenstellen von Beispielanwendungen verwendet wurden.

6. Setup des Plugin-Projekts: Funktionen im Debug-Modus


Da es sich bei diesem Projekt um eine dynamische Bibliothek handelt, muss ein Programm vorhanden sein, das diese Bibliothek während der Ausführung lädt. Dies kann jede Anwendung sein, die OSG verwendet und in der die Funktion aufgerufen wird

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

In diesem Fall wird unser Plugin geladen. Um ein solches Programm nicht selbst zu schreiben, verwenden wir eine vorgefertigte Lösung - den Standard-Osgviewer-Viewer, der im Lieferpaket der Engine enthalten ist. Wenn in der Konsole ausführen

 $ osgviewer piramide.pmd 

dann wird auch das Plugin ausgelöst. Geben Sie in den Einstellungen für den Projektstart den Pfad zu osgviewerd als Arbeitsverzeichnis an, geben Sie das Verzeichnis an, in dem sich die Datei piramide.pmd befindet, und geben Sie dieselbe Datei in den Befehlszeilenoptionen von osgviewer an



Jetzt können wir das Plugin ausführen und es direkt von der QtCreator-IDE aus debuggen.

6. Wir implementieren das Plugin Framework


Dieses Beispiel verallgemeinert in gewissem Maße das Wissen, das wir bereits aus früheren Lektionen über OSG erhalten haben. Wenn wir ein Plugin schreiben, müssen wir

  1. Wählen Sie eine Datenstruktur aus, um Informationen zur Modellgeometrie zu speichern, die aus einer Modelldatei gelesen wurden
  2. Lesen und analysieren Sie die Modelldatendatei
  3. Konfigurieren Sie das geometrische Objekt osg :: Drawable basierend auf den aus der Datei gelesenen Daten korrekt
  4. Erstellen Sie einen Szenenuntergraphen für ein geladenes Modell

Daher werde ich traditionell den gesamten Quellcode des Plugins angeben

Osgdb_pmd Plugin
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 ) 


Zunächst kümmern wir uns um die Strukturen zum Speichern von Geometriedaten.

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

- beschreibt die Fläche, die durch die Liste der Indizes der zu dieser Fläche gehörenden Scheitelpunkte definiert ist. Das Modell als Ganzes wird durch eine solche Struktur beschrieben

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

Die Struktur besteht aus Elementvariablen zum Speichern von Daten: Scheitelpunkte - zum Speichern eines Arrays von Scheitelpunkten eines geometrischen Objekts; Normalen - eine Reihe von Normalen zu den Flächen des Objekts; Gesichter - eine Liste der Gesichter des Objekts. Der Strukturkonstruktor initialisiert sofort intelligente Zeiger

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

Darüber hinaus enthält die Struktur eine Methode, mit der Sie den Normalenvektor für das Gesicht berechnen können calcFaceNormal () als Parameter, der eine Struktur verwendet, die das Gesicht beschreibt. Wir werden noch nicht auf Details der Implementierung dieser Methode eingehen, wir werden sie etwas später analysieren.

Daher haben wir uns für die Strukturen entschieden, in denen wir die Geometriedaten speichern werden. Schreiben wir nun das Framework unseres Plugins, nämlich die Implementierung der Vererbungsklasse 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; }; 

Wie in der Beschreibung der API für die Entwicklung von Plugins empfohlen, definieren wir in dieser Klasse die Methoden zum Lesen von Daten aus einer Datei und zum Konvertieren in einen Untergraphen der Szene neu. Die readNode () -Methode führt zwei Überladungen durch - eine akzeptiert den Dateinamen als Eingabe, die andere empfängt Standardeingaben. Der Klassenkonstruktor definiert die vom Plugin unterstützten Dateierweiterungen

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

Die erste Überladung der Methode readNode () analysiert die Richtigkeit des Dateinamens und des Pfads zu dieser Datei, ordnet der Datei einen Standardeingabestream zu und ruft die zweite Überladung auf, die die Hauptarbeit erledigt

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

In der zweiten Überladung implementieren wir den Objekterzeugungsalgorithmus für 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(); } 

Rufen Sie am Ende der Datei main.cpp das Makro REGISTER_OSGPLUGIN () auf.

 REGISTER_OSGPLUGIN( pmd, ReaderWriterPMD ) 

Dieses Makro generiert zusätzlichen Code, mit dem OSG in Form der osgDB-Bibliothek ein Objekt vom Typ ReaderWriterPMD erstellen und seine Methoden zum Laden von Dateien vom Typ pmd aufrufen kann. Damit das Plugin-Framework fertig ist, bleibt die Sache für kleine - das Laden und Parsen der pmd-Datei zu implementieren.

7. Parsim 3D-Modelldatei


Jetzt beruht die gesamte Funktionalität des Plugins auf der Implementierung der parsePMD () -Methode

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

Die ParseLine () -Methode analysiert die Zeile der pmd-Datei

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

Diese Methode verwandelt die Zeichenfolge "Vertex: 1.0 -1.0 0.0" in eine Liste der beiden Zeilen "Vertex" und "1.0 -1.0 0.0". In der ersten Zeile identifizieren wir den Datentyp - den Scheitelpunkt oder die Fläche, aus der zweiten extrahieren wir die Daten auf den Koordinaten des Scheitelpunkts. Um die Funktionsweise dieser Methode sicherzustellen, benötigen wir die Hilfsfunktion delete_symbol (), die das angegebene Zeichen aus der Zeichenfolge entfernt und eine Zeichenfolge zurückgibt, die dieses Zeichen nicht enthält

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

Das heißt, jetzt haben wir alle Funktionen unseres Plugins implementiert und können es testen.

8. Testen des Plugins


Wir kompilieren das Plugin und führen das Debugging aus (F5). Es wird eine Debug-Version des Standard-Osgviewerd-Viewers gestartet, die die an sie übergebene Datei piramide.pmd analysiert, unser Plugin lädt und die Methode readNode () aufruft. Wenn wir alles richtig gemacht haben, erhalten wir ein solches Ergebnis.



Es stellt sich heraus, dass die Liste der Eckpunkte und Flächen in unserer erfundenen Datei des 3D-Modells eine viereckige Pyramide verbarg.

Warum haben wir die Normalen selbst berechnet? In einer der Lektionen wurde uns die folgende Methode zur automatischen Berechnung geglätteter Normalen angeboten

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

Wir wenden diese Funktion in unserem Beispiel an, anstatt unsere eigenen Normalen zuzuweisen

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

und wir erhalten das folgende Ergebnis: Die



Normalen beeinflussen die Berechnung der Beleuchtung des Modells, und wir sehen, dass in dieser Situation die geglätteten Normalen zu falschen Ergebnissen der Berechnung der Beleuchtung der Pyramide führen. Aus diesem Grund haben wir unser Fahrrad auf die Berechnung von Normalen angewendet. Aber ich denke, dass es den Rahmen dieser Lektion sprengt, die Nuancen zu erklären.

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


All Articles