
Présentation
Quelque part dans les leçons précédentes, il a déjà été dit que l'OSG prend en charge le chargement de divers types de ressources telles que les images raster, les modèles 3D de différents formats ou, par exemple, les polices via son propre système de plug-in. Le plugin OSG est un composant distinct qui étend les fonctionnalités du moteur et possède une interface normalisée au sein de l'OSG. Le plugin est implémenté comme une bibliothèque partagée dynamique (DLL sur Windows, donc sur Linux, etc.). Les noms des bibliothèques de plugins correspondent à une convention spécifique
osgdb_< >.dll
c'est-à-dire que le nom du plugin contient toujours le préfixe osgdb_. Une extension de fichier indique au moteur quel plug-in doit être utilisé pour télécharger un fichier avec cette extension. Par exemple, lorsque nous écrivons une fonction dans le code
osg::ref_ptr<osg::Node> model = osgDB::readNodeFile("cessna.osg");
le moteur voit l'extension osg et charge un plugin nommé osgdb_osg.dll (ou osgdb_osg.so dans le cas de Linux). Le code du plugin fait tout le sale boulot en nous renvoyant un pointeur vers un nœud qui décrit le modèle cessna. De même, essayer de charger une image PNG
osg::ref_ptr<osg:Image> image = osgDB::readImageFile("picture.png");
entraînera le chargement du plugin osgdb_png.dll, qui implémente un algorithme pour lire les données d'une image PNG et placer ces données dans un objet de type osg :: Image.
Toutes les opérations de travail avec des ressources externes sont implémentées par les fonctions de la bibliothèque osgDB, avec lesquelles nous lions invariablement les programmes d'exemple en exemple. Cette bibliothèque repose sur le système de plugins OSG. À ce jour, le package OSG comprend de nombreux plug-ins qui fonctionnent avec la plupart des formats d'image, des modèles 3D et des polices utilisées dans la pratique. Les plugins fournissent à la fois la lecture de données (importation) d'un format spécifique et, dans la plupart des cas, l'écriture de données dans un fichier du format requis (exportation). L'utilitaire osgconv, en particulier, vous permet de convertir des données d'un format à un autre, par exemple, le système de plug-in.
$ osgconv cessna.osg cessna.3ds
il convertit facilement et naturellement le modèle osg cessna au format 3DS, qui peut ensuite être importé dans un éditeur 3D, par exemple, dans Blender (au fait, il existe une
extension pour travailler directement avec osg pour Blender)

Il existe une liste officielle des plugins OSG standard avec une description de leur objectif, mais elle est longue et je suis trop paresseux pour la présenter ici. Il est plus facile de regarder le chemin d'installation de la bibliothèque dans le dossier bin / ospPlugins-xyz, où x, y, z est le numéro de version OSG. À partir du nom du fichier du plugin, il est facile de comprendre quel format il traite.
Si l'OSG est compilé par le compilateur MinGW, un préfixe supplémentaire mingw_ est ajouté au nom standard du plugin, c'est-à-dire que le nom ressemblera à ceci
mingw_osgdb_< >.dll
La version du plugin compilée dans la configuration DEBUG est en outre équipée du suffixe d à la fin du nom, c'est-à-dire que le format sera
osgdb_< >d.dll
ou
mingw_osgdb_< >d.dll
lors de l'assemblage de MinGW.
1. Plugins pseudo-chargeurs
Certains plugins OSG remplissent les fonctions de pseudo-chargeurs - cela signifie qu'ils ne sont pas liés à une extension de fichier spécifique, mais en ajoutant un suffixe à la fin du nom de fichier, vous pouvez spécifier quel plug-in doit être utilisé pour télécharger ce fichier, par exemple
$ osgviewer worldmap.shp.ogr
Dans ce cas, le vrai nom du fichier sur le disque est worldmap.shp - ce fichier stocke une carte du monde au format de fichier de formes ESRI. Le suffixe .ogr indique à la bibliothèque osgDB d'utiliser le plugin osgdb_ogr pour charger ce fichier; sinon, le plugin osgdb_shp sera utilisé.
Un autre bon exemple est le plugin osgdb_ffmpeg. La bibliothèque FFmpeg prend en charge plus de 100 codecs différents. Pour lire l'un d'entre eux, nous pouvons simplement ajouter le suffixe .ffmpeg après le nom du fichier multimédia.
En plus de cela, certains pseudo-chargeurs nous permettent de passer par un suffixe un certain nombre de paramètres qui affectent l'état de l'objet chargé, et nous l'avons déjà rencontré dans un exemple d'animation
node = osgDB::readNodeFile("cessna.osg.0,0,90.rot");
La ligne 0.90 indique au plugin osgdb_osg les paramètres de l'orientation initiale du modèle chargé. Certains pseudo-chargeurs nécessitent des paramètres complètement spécifiques pour fonctionner.
2. API pour développer des plugins tiers
Il est tout à fait logique que, après toute la lecture, vous ayez eu l'idée qu'il ne serait probablement pas difficile d'écrire votre propre plug-in pour OSG, ce qui vous permettrait d'importer un format non standard de modèles ou d'images 3D. Et c'est une vraie pensée! Le mécanisme du plugin est juste conçu pour étendre les fonctionnalités du moteur sans changer l'OSG lui-même. Pour comprendre les principes de base de l'écriture d'un plugin, essayons d'implémenter un exemple simple.
Le développement du plugin consiste à étendre l'interface de lecture / écriture virtuelle fournie par OSG. Cette fonctionnalité est fournie par la classe virtuelle osgDB :: ReaderWriter. Cette classe fournit un certain nombre de méthodes virtuelles redéfinies par le développeur du plugin.
La méthode | La description |
---|
supportsExtensions () | Il accepte deux paramètres de chaîne: l'extension de fichier et la description. La méthode est toujours appelée dans le constructeur de la sous-classe. |
accepteExtension () | Renvoie true si l'extension passée en argument est prise en charge par le plugin |
fileExists () | Vous permet de déterminer si un fichier donné existe (le chemin est passé en paramètre) sur le disque (retourne vrai en cas de succès) |
readNode () | Accepte le nom de fichier et les options en tant qu'objet osgDB :: Option. Les fonctions de lecture des données d'un fichier sont implémentées par le développeur |
writeNode () | Accepte le nom du nœud, le nom de fichier souhaité et les options. Les fonctions d'écriture des données sur disque sont implémentées par le développeur |
readImage () | Lecture des données bitmap du disque |
writeImage () | Écriture d'une image bitmap sur le disque |
L'implémentation de la méthode readNode () peut être décrite par le code suivant
osgDB::ReaderWriter::ReadResult readNode( const std::string &file, const osgDB::Options *options) const {
Il est un peu surprenant qu'au lieu d'un pointeur sur le nœud du graphe de scène, la méthode renvoie le type osgDB :: ReaderWriter :: ReadResult. Ce type est un objet de résultat de lecture et il peut être utilisé comme conteneur de noeud, image, énumérateur d'état (par exemple, FILE_NOT_FOUND), un autre objet spécial ou même comme chaîne de message d'erreur. Il a de nombreux constructeurs implicites pour implémenter les fonctions décrites.
Une autre classe utile est osgDB :: Options. Il peut vous permettre de définir ou d'obtenir une chaîne d'options de chargement à l'aide des méthodes setOptionString () et getOptionString (). La transmission de cette chaîne au constructeur de cette classe comme argument est également autorisée.
Le développeur peut contrôler le comportement du plugin en définissant les paramètres dans la chaîne de paramètres passée lors du chargement de l'objet, par exemple, de cette manière
3. Traitement du flux de données dans le plugin OSG
La classe de base osgDB :: ReaderWriter comprend un ensemble de méthodes qui traitent les données des flux d'entrée / sortie fournies par la bibliothèque C ++ standard. La seule différence entre ces méthodes de lecture / écriture et celles décrites ci-dessus est qu'au lieu du nom de fichier, elles acceptent std :: istream & input streams ou std :: ostream & output stream. L'utilisation d'un flux d'E / S de fichiers est toujours préférable à l'utilisation d'un nom de fichier. Pour effectuer des opérations de lecture de fichiers, nous pouvons utiliser la conception d'interface suivante:
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 {
Après avoir implémenté le plugin, nous pouvons utiliser les fonctions standard osgDB :: readNodeFile () et osgDB :: readImageFile () pour charger des modèles et des images, simplement en spécifiant le chemin du fichier. OSG trouvera et téléchargera le plugin que nous avons écrit.
4. Nous écrivons notre propre plugin
Donc, personne ne nous dérange pour trouver notre propre format de stockage des données sur la géométrie tridimensionnelle, et nous le proposerons
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
Voici au début du fichier une liste de sommets avec leurs coordonnées. Les indices de sommet vont dans l'ordre, à partir de zéro. Après la liste des sommets vient une liste de visages. Chaque face est définie par une liste d'indices de sommets à partir desquels elle est formée. Apparemment rien de compliqué. La tâche consiste à lire ce fichier à partir du disque et à former une géométrie tridimensionnelle sur sa base.
5. Configuration du projet de plugin: construire des fonctionnalités de script
Si avant de créer des applications, nous devons maintenant écrire une bibliothèque dynamique, et pas seulement une bibliothèque, mais un plug-in OSG qui satisfait certaines exigences. Nous allons commencer à remplir ces conditions avec un script de construction de projet qui ressemblera à ceci
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)
Nous analyserons plus en détail les nuances individuelles
TEMPLATE = lib
signifie que nous allons construire la bibliothèque. Pour éviter la génération de liens symboliques à l'aide desquels les problèmes de conflits de version de bibliothèque sont résolus dans les systèmes * nix, nous indiquons au système de construction que cette bibliothèque sera un plug-in, c'est-à-dire qu'elle sera chargée en mémoire "à la volée"
CONFIG += plugin
Ensuite, nous excluons la génération du préfixe lib, qui est ajouté lors de l'utilisation des compilateurs de la famille gcc et est pris en compte par l'environnement d'exécution lors du chargement de la bibliothèque
CONFIG += no_plugin_name_prefix
Définissez le nom du fichier de bibliothèque
TARGET = osgdb_pmd
où pmd est l'extension de fichier du format de modèle 3D que nous avons inventé. De plus, nous devons indiquer que dans le cas d'un assemblage MinGW, le préfixe mingw_
win32-g++: TARGET = $$join(TARGET,,mingw_,)
Spécifiez le chemin de création de la bibliothèque: pour Windows
DESTDIR = $$(OSG_PLUGINS_PATH)
pour linux
DESTDIR = /usr/lib/osgPlugins-3.7.0
Pour Linux, avec une telle indication du chemin (qui est sans aucun doute une béquille, mais je n'ai pas encore trouvé une autre solution), nous donnons le droit d'écrire dans le dossier spécifié avec les plugins OSG d'un utilisateur ordinaire
# chmod 666 /usr/lib/osgPlugins-3.7.0
Tous les autres paramètres de génération sont similaires à ceux utilisés dans l'assemblage d'exemples d'applications précédemment.
6. Configuration du projet de plugin: fonctionnalités du mode débogage
Puisque ce projet est une bibliothèque dynamique, il doit y avoir un programme qui charge cette bibliothèque dans le processus de son exécution. Il peut s'agir de n'importe quelle application utilisant OSG et dans laquelle la fonction sera appelée
node = osdDB::readNodeFile("piramide.pmd");
Dans ce cas, notre plugin se chargera. Afin de ne pas écrire un tel programme par nous-mêmes, nous utiliserons une solution prête à l'emploi - la visionneuse osgviewer standard incluse dans le package de livraison du moteur. Si dans la console exécutez
$ osgviewer piramide.pmd
alors il déclenchera également le plugin. Dans les paramètres de lancement du projet, spécifiez le chemin d'accès à osgviewerd, en tant que répertoire de travail, spécifiez le répertoire où se trouve le fichier piramide.pmd et spécifiez le même fichier dans les options de ligne de commande osgviewer

Nous pouvons maintenant exécuter le plugin et le déboguer directement à partir de l'IDE QtCreator.
6. Nous implémentons le framework de plugins
Cet exemple généralise dans une certaine mesure les connaissances que nous avons déjà reçues sur l'OSG des leçons précédentes. Lors de l'écriture d'un plugin, nous devons
- Sélectionnez une structure de données pour stocker les informations de géométrie du modèle lues dans un fichier de modèle
- Lire et analyser (analyser) le fichier de données du modèle
- Configurer correctement l'objet géométrique osg :: Drawable en fonction des données lues dans le fichier
- Créer un sous-graphique de scène pour un modèle chargé
Donc, par tradition, je donnerai tout le code source du plugin
Plugin Osgdb_pmdmain.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"
Tout d'abord, prenons soin des structures de stockage des données de géométrie.
struct face_t { std::vector<unsigned int> indices; };
- décrit la face définie par la liste des indices des sommets appartenant à cette face. Le modèle dans son ensemble sera décrit par une telle structure
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 structure se compose de variables membres pour stocker des données: sommets - pour stocker un tableau de sommets d'un objet géométrique; normales - un tableau de normales aux faces de l'objet; faces - une liste des visages de l'objet. Le constructeur de la structure initialise immédiatement les pointeurs intelligents
pmd_mesh_t() : vertices(new osg::Vec3Array) , normals(new osg::Vec3Array) { }
De plus, la structure contient une méthode qui vous permet de calculer le vecteur normal de la face calcFaceNormal () comme paramètre, qui prend la structure qui décrit la face. Nous n'entrerons pas encore dans les détails de l'implémentation de cette méthode, nous les analyserons un peu plus tard.
Ainsi, nous avons décidé des structures dans lesquelles nous allons stocker les données de géométrie. Écrivons maintenant le framework de notre plugin, à savoir, nous implémentons la classe héritière 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; };
Comme recommandé dans la description de l'API pour développer des plugins, dans cette classe, nous redéfinissons les méthodes de lecture des données d'un fichier et de les convertir en un sous-graphique de la scène. La méthode readNode () effectue deux surcharges - l'une accepte le nom de fichier en entrée, l'autre reçoit un flux d'entrée standard. Le constructeur de classe définit les extensions de fichiers prises en charge par le plugin
ReaderWriterPMD::ReaderWriterPMD() { supportsExtension("pmd", "PMD model file"); }
La première surcharge de la méthode readNode () analyse l'exactitude du nom de fichier et du chemin d'accès, associe un flux d'entrée standard au fichier et appelle la deuxième surcharge, qui effectue le travail principal
osgDB::ReaderWriter::ReadResult ReaderWriterPMD::readNode( const std::string &filename, const osgDB::Options *options) const {
Dans la deuxième surcharge, nous implémentons l'algorithme de génération d'objet pour OSG
osgDB::ReaderWriter::ReadResult ReaderWriterPMD::readNode( std::istream &stream, const osgDB::Options *options) const { (void) options;
À la fin du fichier main.cpp, appelez la macro REGISTER_OSGPLUGIN ().
REGISTER_OSGPLUGIN( pmd, ReaderWriterPMD )
Cette macro génère du code supplémentaire qui permet à OSG, sous la forme de la bibliothèque osgDB, de construire un objet de type ReaderWriterPMD et d'appeler ses méthodes pour charger des fichiers de type pmd. Ainsi, le framework de plugin est prêt, la chose reste pour les petits - pour implémenter le chargement et l'analyse du fichier pmd.
7. Fichier de modèle Parsim 3D
Maintenant, toutes les fonctionnalités du plugin reposent sur l'implémentation de parsePMD ()
pmd_mesh_t ReaderWriterPMD::parsePMD(std::istream &stream) const { pmd_mesh_t mesh;
parseLine() pmd-
std::vector<std::string> ReaderWriterPMD::parseLine(const std::string &line) const { std::vector<std::string> tokens;
"vertex: 1.0 -1.0 0.0" "vertex" " 1.0 -1.0 0.0". — , . delete_symbol(),
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; }
.
8.
(F5). osgviewerd, piramide.pmd, readNode(). ,

3D- .
?
osgUtil::SmoothingVisitor::smooth(*geom);
,

, . . , .