
Présentation
Cet article ne se concentrera pas tant sur les graphiques que sur la manière dont l'application qui les utilise doit être organisée, en tenant compte des spécificités du moteur OpenSceneGraph et des logiciels qu'il fournit.
Ce n'est un secret pour personne que la clé du succès de tout produit logiciel est une architecture bien conçue qui permet de maintenir et d'étendre le code écrit. En ce sens, le moteur que nous envisageons est à un niveau assez élevé, fournissant au développeur une boîte à outils très large, permettant la construction d'une architecture modulaire flexible.
Cet article est assez long et comprend un aperçu des différents outils et techniques (modèles de conception, si vous le souhaitez) fournis par le moteur de développement. Toutes les sections de l'article sont fournies avec des exemples, dont le code peut être pris dans
mon référentiel .
1. Analyse des options de ligne de commande
En C / C ++, les paramètres de ligne de commande sont transmis via les arguments à la fonction main (). Dans les exemples précédents, nous avons soigneusement marqué ces paramètres comme inutilisés, nous allons maintenant les utiliser pour indiquer à notre programme certaines données au démarrage.
OSG possède des outils d'analyse de ligne de commande intégrés.
Créez l'exemple suivant
Exemple de ligne de commandemain.h#ifndef MAIN_H #define MAIN_H #include <osgDB/ReadFile> #include <osgViewer/Viewer> #endif // MAIN_H
main.cpp #include "main.h" int main(int argc, char *argv[]) { osg::ArgumentParser args(&argc, argv); std::string filename; args.read("--model", filename); osg::ref_ptr<osg::Node> root = osgDB::readNodeFile(filename); osgViewer::Viewer viewer; viewer.setSceneData(root.get()); return viewer.run(); }
Définissez les paramètres de démarrage du programme dans QtCreator

En exécutant le programme pour l'exécution, nous obtenons le résultat (modèle de camion
tiré des mêmes données OpenSceneGraph )

Voyons maintenant un exemple ligne par ligne
osg::ArgumentParser args(&argc, argv);
crée une instance de la classe d'analyseur de ligne de commande osg :: ArgumentParser. Une fois créé, le constructeur de classe reçoit les arguments acceptés par la fonction main () du système d'exploitation.
std::string filename; args.read("--model", filename);
nous analysons les arguments, à savoir, nous recherchons la clé «–model» parmi eux, en mettant sa valeur dans le nom de fichier de la chaîne. Ainsi, en utilisant cette clé, nous transférons le nom du fichier avec un modèle tridimensionnel au programme. Ensuite, nous chargeons ce modèle et l'afficher
osg::ref_ptr<osg::Node> root = osgDB::readNodeFile(filename); osgViewer::Viewer viewer; viewer.setSceneData(root.get()); return viewer.run();
La méthode read () de la classe osg :: ArgumentParser a beaucoup de surcharges, vous permettant de lire non seulement les valeurs de chaîne de la ligne de commande, mais aussi les entiers, les nombres à virgule flottante, les vecteurs, etc. Par exemple, vous pouvez lire un certain paramètre de type float
float size = 0.0f; args.read("--size", size);
Si ce paramètre n'est pas affiché sur la ligne de commande, sa valeur restera telle qu'elle était après l'initialisation de la variable de taille.
2. Mécanisme de notification et d'enregistrement
OpenSceneGraph dispose d'un mécanisme de notification qui vous permet d'afficher des messages de débogage pendant le processus de rendu, ainsi qu'initiés par le développeur. Ceci est d'une grande aide lors du traçage et du débogage d'un programme. Le système de notification OSG prend en charge la sortie des informations de diagnostic (erreurs, avertissements, notifications) au niveau du cœur du moteur et de ses plug-ins. Le développeur peut afficher un message de diagnostic pendant le fonctionnement du programme en utilisant la fonction osg :: notify ().
Cette fonction fonctionne comme un flux de sortie standard de la bibliothèque C ++ standard via une surcharge d'opérateur <<. Il prend le niveau du message comme argument: TOUJOURS, FATAL, WARN, NOTICE, INFO, DEBUG_INFO et DEBUG_FP. Par exemple
osg::notify(osg::WARN) << "Some warning message" << std::endl;
affiche un avertissement avec du texte défini par l'utilisateur.
Les notifications OSG peuvent contenir des informations importantes sur l'état du programme, les extensions du sous-système graphique de l'ordinateur, les problèmes possibles avec le moteur.
Dans certains cas, il est nécessaire de sortir ces données non pas sur la console, mais pour pouvoir rediriger cette sortie vers un fichier (sous la forme d'un journal) ou vers toute autre interface, y compris un widget graphique. Le moteur contient une classe spéciale osg :: NotifyHandler qui fournit la redirection des notifications vers le flux de sortie dont le développeur a besoin.
À l'aide d'un exemple simple, réfléchissez à la manière de rediriger la sortie des notifications, par exemple, vers un fichier journal de texte. Écrivez le code suivant
Exemple de notificationmain.h #ifndef MAIN_H #define MAIN_H #include <osgDB/ReadFile> #include <osgViewer/Viewer> #include <fstream> #endif // MAIN_H
main.cpp #include "main.h" class LogFileHandler : public osg::NotifyHandler { public: LogFileHandler(const std::string &file) { _log.open(file.c_str()); } virtual ~LogFileHandler() { _log.close(); } virtual void notify(osg::NotifySeverity severity, const char *msg) { _log << msg; } protected: std::ofstream _log; }; int main(int argc, char *argv[]) { osg::setNotifyLevel(osg::INFO); osg::setNotifyHandler(new LogFileHandler("../logs/log.txt")); osg::ArgumentParser args(&argc, argv); osg::ref_ptr<osg::Node> root = osgDB::readNodeFiles(args); if (!root) { OSG_FATAL << args.getApplicationName() << ": No data loaded." << std::endl; return -1; } osgViewer::Viewer viewer; viewer.setSceneData(root.get()); return viewer.run(); }
Pour rediriger la sortie, nous écrivons la classe LogFileHandler, qui est le successeur de osg :: NotifyHandler. Le constructeur et le destructeur de cette classe contrôlent l'ouverture et la fermeture du flux de sortie _log auquel le fichier texte est associé. La méthode notify () est une méthode de classe de base similaire que nous avons redéfinie pour afficher dans le fichier les notifications envoyées par OSG pendant le fonctionnement via le paramètre msg.
Classe LogFileHandler class LogFileHandler : public osg::NotifyHandler { public: LogFileHandler(const std::string &file) { _log.open(file.c_str()); } virtual ~LogFileHandler() { _log.close(); } virtual void notify(osg::NotifySeverity severity, const char *msg) { _log << msg; } protected: std::ofstream _log; };
Ensuite, dans le programme principal, effectuez les réglages nécessaires
osg::setNotifyLevel(osg::INFO);
définissez le niveau des notifications INFO, c'est-à-dire la sortie dans le journal de toutes les informations sur le fonctionnement du moteur, y compris les notifications actuelles de fonctionnement normal.
osg::setNotifyHandler(new LogFileHandler("../logs/log.txt"));
installez le gestionnaire de notification. Ensuite, nous traitons les arguments de ligne de commande dans lesquels les chemins d'accès aux modèles chargés sont passés
osg::ArgumentParser args(&argc, argv); osg::ref_ptr<osg::Node> root = osgDB::readNodeFiles(args); if (!root) { OSG_FATAL << args.getApplicationName() << ": No data loaded." << std::endl; return -1; }
Dans le même temps, nous gérons la situation de manque de données sur la ligne de commande, affichant un message dans le journal en mode manuel à l'aide de la macro OSG_FATAL. Exécutez le programme avec les arguments suivants

obtenir la sortie dans un fichier journal comme celui-ci
Exemple de journal OSG Opened DynamicLibrary osgPlugins-3.7.0/mingw_osgdb_osgd.dll CullSettings::readEnvironmentalVariables() CullSettings::readEnvironmentalVariables() Opened DynamicLibrary osgPlugins-3.7.0/mingw_osgdb_deprecated_osgd.dll OSGReaderWriter wrappers loaded OK CullSettings::readEnvironmentalVariables() void StateSet::setGlobalDefaults() void StateSet::setGlobalDefaults() ShaderPipeline disabled. StateSet::setGlobalDefaults() Setting up GL2 compatible shaders CullSettings::readEnvironmentalVariables() CullSettings::readEnvironmentalVariables() CullSettings::readEnvironmentalVariables() CullSettings::readEnvironmentalVariables() ShaderComposer::ShaderComposer() 0xa5ce8f0 CullSettings::readEnvironmentalVariables() ShaderComposer::ShaderComposer() 0xa5ce330 View::setSceneData() Reusing existing scene0xa514220 CameraManipulator::computeHomePosition(0, 0) boundingSphere.center() = (-6.40034 1.96225 0.000795364) boundingSphere.radius() = 16.6002 CameraManipulator::computeHomePosition(0xa52f138, 0) boundingSphere.center() = (-6.40034 1.96225 0.000795364) boundingSphere.radius() = 16.6002 Viewer::realize() - No valid contexts found, setting up view across all screens. Applying osgViewer::ViewConfig : AcrossAllScreens . . . . ShaderComposer::~ShaderComposer() 0xa5ce330 ShaderComposer::~ShaderComposer() 0xa5ce8f0 ShaderComposer::~ShaderComposer() 0xa5d6228 close(0x1)0xa5d3e50 close(0)0xa5d3e50 ContextData::unregisterGraphicsContext 0xa5d3e50 DatabasePager::RequestQueue::~RequestQueue() Destructing queue. DatabasePager::RequestQueue::~RequestQueue() Destructing queue. DatabasePager::RequestQueue::~RequestQueue() Destructing queue. DatabasePager::RequestQueue::~RequestQueue() Destructing queue. ShaderComposer::~ShaderComposer() 0xa5de4e0 close(0x1)0xa5ddba0 close(0)0xa5ddba0 ContextData::unregisterGraphicsContext 0xa5ddba0 Done destructing osg::View DatabasePager::RequestQueue::~RequestQueue() Destructing queue. DatabasePager::RequestQueue::~RequestQueue() Destructing queue. DatabasePager::RequestQueue::~RequestQueue() Destructing queue. DatabasePager::RequestQueue::~RequestQueue() Destructing queue. Closing DynamicLibrary osgPlugins-3.7.0/mingw_osgdb_osgd.dll Closing DynamicLibrary osgPlugins-3.7.0/mingw_osgdb_deprecated_osgd.dll
Peu importe que ces informations vous semblent inutiles pour le moment - à l'avenir, une telle conclusion peut aider à déboguer des erreurs dans votre programme.
Par défaut, OSG envoie des messages à la sortie standard std :: cout et des messages d'erreur au flux std :: cerr. Cependant, en remplaçant le gestionnaire de notification, comme indiqué dans l'exemple, cette sortie peut être redirigée vers n'importe quel flux de sortie, y compris les éléments GUI.
Gardez à l'esprit que lors de la définition d'un niveau élevé de notifications (par exemple, FATAL), le système ignore toutes les notifications d'un niveau inférieur. Par exemple, dans un cas similaire
osg::setNotifyLevel(osg::FATAL); . . . osg::notify(osg::WARN) << "Some message." << std::endl;
un message personnalisé ne sera tout simplement pas affiché.
3. Interception d'attributs géométriques
La classe osg :: Geometry gère un ensemble de données décrivant les sommets et affiche un maillage polygonal à l'aide d'un ensemble ordonné de primitives. Cependant, cette classe n'a aucune idée des éléments de la topologie du modèle tels que les faces, les arêtes et la relation entre eux. Cette nuance empêche la mise en œuvre de choses telles que le déplacement de certains visages, par exemple, lors de l'animation de modèles. OSG ne prend actuellement pas en charge cette fonctionnalité.
Cependant, le moteur implémente un certain nombre de foncteurs qui vous permettent de relire les attributs de géométrie de n'importe quel objet et de les utiliser pour modéliser la topologie du maillage polygonal. En C ++, un foncteur est une construction qui vous permet d'utiliser un objet comme fonction.
La classe osg :: Drawable fournit au développeur quatre types de foncteurs:
- osg :: Drawable :: AttributeFunctor - lit les attributs des sommets comme un tableau de pointeurs. Il dispose d'un certain nombre de méthodes virtuelles pour appliquer des attributs de sommet de différents types de données. Pour utiliser ce foncteur, vous devez décrire la classe et remplacer une ou plusieurs de ses méthodes, dans lesquelles les actions requises par le développeur sont effectuées
virtual void apply( osg::Drawable::AttributeType type, unsigned int size, osg::Vec3* ptr ) {
- osg :: Drawable :: ConstAttributeFunctor - version en lecture seule du foncteur précédent: un pointeur vers un tableau de vecteurs est passé en paramètre constant
- osg :: PrimitiveFunctor - imite le processus de rendu des objets OpenGL. Sous prétexte de restituer un objet, les méthodes de foncteur remplacées par le développeur sont appelées. Ce foncteur possède deux sous-classes de modèles importantes: osg :: TemplatePrimitiveFunctor <> et osg :: TriangleFunctor <>. Ces classes reçoivent des sommets primitifs en tant que paramètres et les transmettent aux méthodes utilisateur à l'aide de l'opérateur operator ().
- osg :: PrimitiveIndexFunctor - effectue les mêmes actions que le foncteur précédent, mais accepte les indices de vertex de la primitive comme paramètre.
Les classes dérivées d'osg :: Drawable, telles que osg :: ShapeDrawable et osg :: Geometry, ont une méthode accept () pour appliquer divers foncteurs.
4. Exemple d'utilisation du foncteur primitif
Nous illustrons la fonctionnalité décrite en utilisant l'exemple de collecte d'informations sur les faces triangulaires et les points de certaines géométries que nous avons précédemment déterminées.
Exemple de foncteurmain.h #ifndef MAIN_H #define MAIN_H #include <osg/Geode> #include <osg/Geometry> #include <osg/TriangleFunctor> #include <osgViewer/Viewer> #include <iostream> #endif
main.cpp #include "main.h" std::string vec2str(const osg::Vec3 &v) { std::string tmp = std::to_string(vx()); tmp += " "; tmp += std::to_string(vy()); tmp += " "; tmp += std::to_string(vz()); return tmp; } struct FaceCollector { void operator()(const osg::Vec3 &v1, const osg::Vec3 &v2, const osg::Vec3 &v3) { std::cout << "Face vertices: " << vec2str(v1) << "; " << vec2str(v2) << "; " << vec2str(v3) << std::endl; } }; int main(int argc, char *argv[]) { (void) argc; (void) argv; osg::ref_ptr<osg::Vec3Array> vertices = new osg::Vec3Array; vertices->push_back( osg::Vec3(0.0f, 0.0f, 0.0f) ); vertices->push_back( osg::Vec3(0.0f, 0.0f, 1.0f) ); vertices->push_back( osg::Vec3(1.0f, 0.0f, 0.0f) ); vertices->push_back( osg::Vec3(1.0f, 0.0f, 1.5f) ); vertices->push_back( osg::Vec3(2.0f, 0.0f, 0.0f) ); vertices->push_back( osg::Vec3(2.0f, 0.0f, 1.0f) ); vertices->push_back( osg::Vec3(3.0f, 0.0f, 0.0f) ); vertices->push_back( osg::Vec3(3.0f, 0.0f, 1.5f) ); vertices->push_back( osg::Vec3(4.0f, 0.0f, 0.0f) ); vertices->push_back( osg::Vec3(4.0f, 0.0f, 1.0f) ); osg::ref_ptr<osg::Vec3Array> normals = new osg::Vec3Array; normals->push_back( osg::Vec3(0.0f, -1.0f, 0.0f) ); osg::ref_ptr<osg::Geometry> geom = new osg::Geometry; geom->setVertexArray(vertices.get()); geom->setNormalArray(normals.get()); geom->setNormalBinding(osg::Geometry::BIND_OVERALL); geom->addPrimitiveSet(new osg::DrawArrays(GL_QUAD_STRIP, 0, 10)); osg::ref_ptr<osg::Geode> root = new osg::Geode; root->addDrawable(geom.get()); osgViewer::Viewer viewer; viewer.setSceneData(root.get()); osg::TriangleFunctor<FaceCollector> functor; geom->accept(functor); return viewer.run(); }
En omettant le processus de création de géométrie que nous considérons plusieurs fois, prêtons attention aux points suivants. Nous définissons une structure FaceCollector pour laquelle nous redéfinissons operator () comme suit
struct FaceCollector { void operator()(const osg::Vec3 &v1, const osg::Vec3 &v2, const osg::Vec3 &v3) { std::cout << "Face vertices: " << vec2str(v1) << "; " << vec2str(v2) << "; " << vec2str(v3) << std::endl; } };
Cet opérateur, une fois appelé, affichera les coordonnées des trois sommets qui lui sont transmis par le moteur. La fonction vec2str est requise pour traduire les composants du vecteur osg :: Vec3 en std :: string. Pour appeler le foncteur, créez-en une instance et passez-la à l'objet géométrie via la méthode accept ()
osg::TriangleFunctor<FaceCollector> functor; geom->accept(functor);
Cet appel, comme mentionné ci-dessus, imite le rendu de la géométrie, remplaçant le dessin lui-même en appelant une méthode fonctor redéfinie. Dans ce cas, il sera appelé lors du "dessin" de chacun des triangles qui composent la géométrie de l'exemple.
Sur l'écran, nous obtenons une telle géométrie

et un tel échappement à la console
Face vertices: 0.000000 0.000000 0.000000; 0.000000 0.000000 1.000000; 1.000000 0.000000 0.000000 Face vertices: 0.000000 0.000000 1.000000; 1.000000 0.000000 1.500000; 1.000000 0.000000 0.000000 Face vertices: 1.000000 0.000000 0.000000; 1.000000 0.000000 1.500000; 2.000000 0.000000 0.000000 Face vertices: 1.000000 0.000000 1.500000; 2.000000 0.000000 1.000000; 2.000000 0.000000 0.000000 Face vertices: 2.000000 0.000000 0.000000; 2.000000 0.000000 1.000000; 3.000000 0.000000 0.000000 Face vertices: 2.000000 0.000000 1.000000; 3.000000 0.000000 1.500000; 3.000000 0.000000 0.000000 Face vertices: 3.000000 0.000000 0.000000; 3.000000 0.000000 1.500000; 4.000000 0.000000 0.000000 Face vertices: 3.000000 0.000000 1.500000; 4.000000 0.000000 1.000000; 4.000000 0.000000 0.000000
En fait, lors de l'appel à geom-> accept (...), les triangles ne sont pas rendus, les appels OpenGL sont simulés, et au lieu d'eux des données sur les sommets du triangle, dont le rendu est simulé

La classe osg :: TemplatePrimitiveFunctor collecte des données non seulement sur les triangles, mais également sur toutes les autres primitives OpenGL. Pour implémenter le traitement de ces données, vous devez remplacer les opérateurs suivants dans l'argument modèle
5. Le modèle de visiteur
Le modèle de visiteur est utilisé pour accéder aux opérations de modification des éléments du graphique de scène sans modifier les classes de ces éléments. La classe visiteur implémente toutes les fonctions virtuelles pertinentes pour les appliquer à différents types d'éléments via un mécanisme de double répartition. À l'aide de ce mécanisme, le développeur peut créer sa propre instance de visiteur en implémentant la fonctionnalité dont il a besoin à l'aide d'opérateurs spéciaux et lier le visiteur à divers types d'éléments de graphique de scène à la volée, sans modifier la fonctionnalité des éléments eux-mêmes. Il s'agit d'un excellent moyen d'étendre les fonctionnalités d'un élément sans définir de sous-classes de ces éléments.
Pour implémenter ce mécanisme dans OSG, la classe osg :: NodeVisitor est définie. La classe héritée d'osg :: NodeVisitor se déplace dans le graphique de la scène, visite chaque nœud et lui applique les opérations définies par le développeur. Il s'agit de la classe principale utilisée pour intervenir dans le processus de mise à jour des nœuds et de découpage des nœuds invisibles, ainsi que pour appliquer d'autres opérations liées à la modification de la géométrie des nœuds de scène, telles que osgUtil :: SmoothingVisitor, osgUtil :: Simplifier et osgUtil :: TriStripVisitor.
Pour sous-classer le visiteur, nous devons remplacer une ou plusieurs méthodes apply () surchargées virtuelles fournies par la classe de base osg :: NodeVisitor. La plupart des principaux types de nœuds OSG ont ces méthodes. Le visiteur appellera automatiquement la méthode apply () pour chacun des nœuds visités lors de la traversée du graphe de la scène. Le développeur remplace la méthode apply () pour chacun des types de nœuds dont il a besoin.
Dans l'implémentation de la méthode apply (), le développeur, au moment approprié, doit appeler la méthode traverse () de la classe de base osg :: NodeVisitor. Cela lance la transition du visiteur vers le nœud suivant, soit un enfant ou un voisin au niveau de la hiérarchie, si le nœud actuel ne possède aucun nœud enfant vers lequel la transition peut être effectuée. L'absence d'un appel à traverse () signifie arrêter la traversée du graphe de scène et le reste du graphe de scène est ignoré.
Les surcharges de la méthode apply () ont des formats unifiés
virtual void apply( osg::Node& ); virtual void apply( osg::Geode& ); virtual void apply( osg::Group& ); virtual void apply( osg::Transform& );
Pour contourner le sous-graphique du nœud actuel de l'objet visiteur, vous devez définir le mode d'analyse, par exemple,
ExampleVisitor visitor; visitor->setTraversalMode( osg::NodeVisitor::TRAVERSE_ALL_CHILDREN ); node->accept( visitor );
Le mode de contournement est défini par plusieurs énumérateurs
- TRAVERSE_ALL_CHILDREN - se déplaçant à travers tous les nœuds enfants.
- TRAVERSE_PARENTS - repasse du nœud actuel, n'atteignant pas le nœud racine
- TRAVERSE_ACTIVE_CHILDREN - contourne exclusivement les nœuds actifs, c'est-à-dire ceux dont la visibilité est activée via le nœud osg :: Switch.
6. Analyse de la structure du cessna brûlant
Le développeur peut toujours analyser la partie du graphique de scène générée par le modèle chargé à partir du fichier.
Exemple de foncteurmain.h #ifndef MAIN_H #define MAIN_H #include <osgDB/ReadFile> #include <osgViewer/Viewer> #include <iostream> #endif
main.cpp #include "main.h"
Nous créons la classe InfoVisitor, en l'héritant d'osg :: NodeVisitor
class InfoVisitor : public osg::NodeVisitor { public: InfoVisitor() : _level(0) { setTraversalMode(osg::NodeVisitor::TRAVERSE_ALL_CHILDREN); } std::string spaces() { return std::string(_level * 2, ' '); } virtual void apply(osg::Node &node); virtual void apply(osg::Geode &geode); protected: unsigned int _level; };
La propriété _level protégée pointera vers le niveau du graphique de scène où se trouve actuellement notre classe de visiteurs. Dans le constructeur, initialisez le compteur de niveau et définissez le mode de traversée des nœuds - pour contourner tous les nœuds enfants.
Redéfinissez maintenant les méthodes apply () pour les nœuds
void InfoVisitor::apply(osg::Node &node) { std::cout << spaces() << node.libraryName() << "::" << node.className() << std::endl; _level++; traverse(node); _level--; }
Ici, nous afficherons le type du nœud actuel. La méthode libraryName () pour le nœud affiche le nom de la bibliothèque OSG où ce nœud est implémenté, et la méthode className affiche le nom de la classe de nœud. Ces méthodes sont implémentées grâce à l'utilisation de macros dans le code des bibliothèques OSG.
std::cout << spaces() << node.libraryName() << "::" << node.className() << std::endl;
Après cela, nous augmentons le compteur de niveau du graphique et appelons la méthode traverse (), initiant une transition vers un niveau supérieur, vers le nœud enfant. Après être revenu de traverse (), nous diminuons à nouveau la valeur du compteur. Il est facile de deviner que traverse () lance un appel répété à la méthode apply (), déjà traversée () pour un sous-graphe à partir du nœud courant. Nous obtenons une exécution récursive des visiteurs jusqu'à ce que nous atteignions les nœuds d'extrémité du graphique de scène.
Pour un nœud d'extrémité de type osg :: Geode, sa surcharge de la méthode apply () est surchargée
void InfoVisitor::apply(osg::Geode &geode) { std::cout << spaces() << geode.libraryName() << "::" << geode.className() << std::endl; _level++; for (unsigned int i = 0; i < geode.getNumDrawables(); ++i) { osg::Drawable *drawable = geode.getDrawable(i); std::cout << spaces() << drawable->libraryName() << "::" << drawable->className() << std::endl; } traverse(geode); _level--; }
avec un code fonctionnant de la même manière, sauf que nous affichons des données sur tous les objets géométriques attachés au nœud géométrique actuel
for (unsigned int i = 0; i < geode.getNumDrawables(); ++i) { osg::Drawable *drawable = geode.getDrawable(i); std::cout << spaces() << drawable->libraryName() << "::" << drawable->className() << std::endl; }
Dans la fonction main (), nous traitons les arguments de ligne de commande à travers lesquels nous passons une liste de modèles chargés dans la scène et formons la scène
osg::ArgumentParser args(&argc, argv); osg::ref_ptr<osg::Node> root = osgDB::readNodeFiles(args); if (!root.valid()) { OSG_FATAL << args.getApplicationName() << ": No data leaded. " << std::endl; return -1; }
Dans le même temps, nous traitons les erreurs liées à l'absence de noms de fichiers de modèle sur la ligne de commande. Maintenant, nous créons une classe de visiteurs et la passons au graphe de scène pour l'exécuter
InfoVisitor infoVisitor; root->accept(infoVisitor);
Voici les étapes pour lancer la visionneuse, ce que nous avons déjà fait plusieurs fois. Après avoir démarré le programme avec des paramètres
$ visitor ../data/cessnafire.osg
nous verrons la sortie suivante sur la console
osg::Group osg::MatrixTransform osg::Geode osg::Geometry osg::Geometry osg::MatrixTransform osgParticle::ModularEmitter osgParticle::ModularEmitter osgParticle::ParticleSystemUpdater osg::Geode osgParticle::ParticleSystem osgParticle::ParticleSystem osgParticle::ParticleSystem osgParticle::ParticleSystem
En fait, nous avons obtenu un arbre complet de la scène chargée. Excusez-moi, où sont autant de nœuds? Tout est très simple - les modèles au format * .osg sont eux-mêmes des conteneurs qui stockent non seulement des données sur la géométrie du modèle, mais également d'autres informations sur sa structure sous la forme d'un sous-graphique de la scène OSG. La géométrie du modèle, les transformations, les effets de particules qui réalisent la fumée et la flamme sont tous des nœuds du graphique de scène OSG.
Toute scène peut être téléchargée à partir de * .osg ou déchargée de la visionneuse au format * .osg.Ceci est un exemple simple d'application de la mécanique des visiteurs. En fait, à l'intérieur des visiteurs, vous pouvez effectuer de nombreuses opérations pour modifier les nœuds lorsque le programme est en cours d'exécution.7. Contrôle du comportement des nœuds dans le graphe de scène en remplaçant la méthode traverse ()
Une façon importante de travailler avec OSG est de remplacer la méthode traverse (). Cette méthode est appelée chaque fois qu'une image est dessinée. Ils acceptent un paramètre de type osg :: NodeVisitor & qui signale quel passage du graphe de scène est actuellement en cours (mise à jour, traitement d'événement ou écrêtage). La plupart des hôtes OSG remplacent cette méthode pour implémenter leurs fonctionnalités.Il ne faut pas oublier que remplacer la méthode traverse () peut être dangereux, car il affecte le processus de traversée du graphe de la scène et peut conduire à un affichage incorrect de la scène. Il est également gênant si vous souhaitez ajouter de nouvelles fonctionnalités à plusieurs types de nœuds. Dans ce cas, des rappels de nœuds sont utilisés, dont la conversation ira un peu plus bas.Nous savons déjà que le nœud osg :: Switch peut contrôler l'affichage de ses nœuds enfants, y compris l'affichage de certains nœuds et désactiver l'affichage des autres. Mais il ne sait pas comment le faire automatiquement, nous allons donc créer un nouveau nœud basé sur l'ancien, qui basculera entre les nœuds enfants à différents moments, conformément à la valeur du compteur interne.Exemple Animswitchmain.h #ifndef MAIN_H #define MAIN_H #include <osg/Switch> #include <osgDB/ReadFile> #include <osgViewer/Viewer> #endif
main.cpp #include "main.h"
Jetons un œil à cet exemple. Nous créons une nouvelle classe AnimatingSwitch qui hérite d'osg :: Switch. class AnimatingSwitch : public osg::Switch { public: AnimatingSwitch() : osg::Switch(), _count(0) {} AnimatingSwitch(const AnimatingSwitch ©, const osg::CopyOp ©op = osg::CopyOp::SHALLOW_COPY) : osg::Switch(copy, copyop), _count(copy._count) {} META_Node(osg, AnimatingSwitch); virtual void traverse(osg::NodeVisitor &nv); protected: unsigned int _count; }; void AnimatingSwitch::traverse(osg::NodeVisitor &nv) { if (!((++_count) % 60) ) { setValue(0, !getValue(0)); setValue(1, !getValue(1)); } osg::Switch::traverse(nv); }
Cette classe contient le constructeur par défaut. AnimatingSwitch() : osg::Switch(), _count(0) {}
et constructeur pour la copie, créé conformément aux exigences de l'OSG AnimatingSwitch(const AnimatingSwitch ©, const osg::CopyOp ©op = osg::CopyOp::SHALLOW_COPY) : osg::Switch(copy, copyop), _count(copy._count) {}
Le constructeur pour la copie doit contenir comme paramètres: une référence constante à l'instance de classe à copier et le paramètre osg :: CopyOp spécifiant les paramètres de copie de la classe. Des lettres assez étranges suivent META_Node(osg, AnimatingSwitch);
Il s'agit d'une macro qui forme la structure nécessaire pour le descendant d'une classe dérivée de osg :: Node. Jusqu'à ce que nous attachions de l'importance à cette macro, il est important qu'elle soit présente lors de l'héritage d'osg :: Switch lors de la définition de toutes les classes descendantes. La classe contient le champ protégé _count - le compteur même sur lequel nous basculons. Nous implémentons la commutation lors de la substitution de la méthode traverse () void AnimatingSwitch::traverse(osg::NodeVisitor &nv) { if (!((++_count) % 60) ) { setValue(0, !getValue(0)); setValue(1, !getValue(1)); } osg::Switch::traverse(nv); }
La commutation de l'état d'affichage des nœuds se produira chaque fois que la valeur du compteur (incrémentation de chaque appel de méthode) est un multiple de 60. Nous compilons l'exemple et l'exécutonsÉtant donné que la méthode traverse () est constamment redéfinie pour divers types de nœuds, elle devrait fournir un mécanisme pour obtenir des matrices de transformation et rendre les états pour une utilisation ultérieure par leur algorithme surchargé. Le paramètre d'entrée osg :: NodeVisitor est la clé de diverses opérations avec des nœuds. En particulier, il indique le type de traversée actuelle du graphique de scène, comme la mise à jour, le traitement des événements et l'écrêtage des faces invisibles. Les deux premiers sont liés aux rappels de nœuds et seront pris en compte lors de l'étude de l'animation.La passe de détourage peut être identifiée en convertissant l'objet osg :: NodeVisitor en objet osg :: CullVisitor osgUtil::CullVisitor *cv = dynamic_cast<osgUtil::CullVisitor *>(&nv); if (cv) {
8. Mécanisme de rappel
Dans l' article précédent, nous avons implémenté l'animation d'un objet de scène en modifiant les paramètres de sa transformation à l'intérieur du cycle de rendu de scène. Comme cela a été mentionné à plusieurs reprises, cette approche contient un comportement d'application potentiellement dangereux dans le rendu multi-thread. Pour résoudre ce problème, un mécanisme de rappel est utilisé lors de la traversée du graphe de scène.Il existe plusieurs types de rappels dans le moteur. Les rappels sont implémentés par des classes spéciales, parmi lesquelles osg :: NodeCallback est conçu pour gérer le processus de mise à jour des nœuds de scène, et osg :: Drawable :: UpdateCallback, osg :: Drawable :: EventCallback et osg :: Drawable: CullCallback - exécute les mêmes fonctions, mais pour les objets de géométrie.La classe osg :: NodeCallback possède un opérateur virtual () remplaçable fourni par le développeur pour implémenter ses propres fonctionnalités. Pour que le rappel fonctionne, vous devez attacher une instance de la classe d'appel au nœud pour lequel il sera traité en appelant la méthode setUpdateCallback () ou addUpdateCallback (). L'opérateur operator () est automatiquement appelé lors de la mise à jour des nœuds du graphe de scène lors du rendu de chaque image.Le tableau suivant répertorie les rappels disponibles pour le développeur dans OSG.Prénom | Fonction de rappel | Méthode virtuelle | Méthode pour attacher à un objet |
---|
Mise à jour du nœud | osg :: NodeCallback | opérateur () | osg :: Node :: setUpdateCallback () |
Événement de nœud | osg :: NodeCallback | opérateur () | osg :: Node :: setEventCallback () |
Écrêtage des nœuds | osg :: NodeCallback | opérateur () | osg :: Node :: setCullCallback () |
Mise à jour de la géométrie | osg::Drawable::UpdateCallback | update() | osg::Drawable::setUpdateCallback() |
| osg::Drawable::EventCallback | event() | osg::Drawable::setEventCallback() |
| osg::Drawable::CullCallback | cull() | osg::Drawable::setCullCallback() |
| osg::StateAttributeCallback | operator() | osg::StateAttribute::setUpdateCallback() |
| osg::StateAttributeCallback | operator() | osg::StateAttribute::setEventCallback() |
| osg::Uniform::Callback | operator() | osg::Uniform::setUpdateCallback() |
| osg::Uniform::Callback | operator() | osg::Uniform::setEvevtCallback() |
| osg::Camera::DrawCallback | operator() | osg::Camera::PreDrawCallback() |
| osg::Camera::DrawCallback | operator() | osg::Camera::PostDrawCallback() |
9. osg::Switch
Un peu plus haut, nous avons écrit un exemple avec la commutation de deux modèles d'avion. Nous allons maintenant répéter cet exemple, mais nous ferons tout correctement en utilisant le mécanisme de rappel OSG.Rappel avec exemplemain.h #ifndef MAIN_H #define MAIN_H #include <osg/Switch> #include <osgDB/ReadFile> #include <osgViewer/Viewer> #endif
main.cpp #include "main.h"
Vous devez créer une classe héritant d'osg :: NodeCallback, qui contrôle le nœud osg :: Switch class SwitchingCallback : public osg::NodeCallback { public: SwitchingCallback() : _count(0) {} virtual void operator()(osg::Node *node, osg::NodeVisitor *nv); protected: unsigned int _count; };
Le compteur _count contrôlera la commutation du nœud osg :: Switch du mappage d'un nœud enfant à un autre, en fonction de sa valeur. Dans le constructeur, nous initialisons le compteur et redéfinissons la méthode virtual operator () void SwitchingCallback::operator()(osg::Node *node, osg::NodeVisitor *nv) { osg::Switch *switchNode = static_cast<osg::Switch *>(node); if ( !((++_count) % 60) && switchNode ) { switchNode->setValue(0, !switchNode->getValue(0)); switchNode->setValue(1, !switchNode->getValue(0)); } traverse(node, nv); }
Le nœud sur lequel l'appel a fonctionné lui est transmis via le paramètre de nœud. Puisque nous savons avec certitude qu'il s'agira d'un nœud de type osg :: Switch, nous effectuons une conversion statique du pointeur vers le nœud vers le pointeur vers le nœud du commutateur osg::Switch *switchNode = static_cast<osg::Switch *>(node);
Nous basculerons les nœuds enfants affichés avec la valeur valide de ce pointeur, et lorsque la valeur du compteur est un multiple de 60 if ( !((++_count) % 60) && switchNode ) { switchNode->setValue(0, !switchNode->getValue(0)); switchNode->setValue(1, !switchNode->getValue(0)); }
N'oubliez pas d'appeler la méthode traverse () pour continuer la traversée récursive du graphe de scène traverse(node, nv);
Le reste du code du programme est trivial, à l'exception de la ligne root->setUpdateCallback( new SwitchingCallback );
où nous affectons le rappel que nous avons créé au nœud racine de type osg :: Switch. Le programme fonctionne de manière similaire à l'exemple précédentJusqu'à présent, nous avons utilisé la mystérieuse méthode traverse () à deux fins: remplacer cette méthode dans les classes successives et appeler cette méthode sur la classe osg :: NodeVisitor pour continuer à parcourir le graphe de la scène.Dans l'exemple que nous venons d'examiner, nous utilisons la troisième option d'appeler traverse (), en passant un pointeur vers le nœud et un pointeur vers l'instance de visiteur comme paramètres. Comme dans les deux premiers cas, s'il n'y a pas d'appel à traverse () sur ce noeud, l'analyse du graphe de scène sera arrêtée.Les méthodes addUpdateCallback () servent également à ajouter un rappel au nœud. Contrairement à setUpdateCallback (), il est utilisé pour ajouter un autre rappel aux appels existants. Ainsi, il peut y avoir plusieurs rappels pour le même nœud.Conclusion
Nous avons examiné les techniques de base utilisées dans le développement d'applications à l'aide du moteur graphique OpenSceneGraph. Cependant, c'est loin de tous les points que j'aimerais aborder (malgré le fait que l'article s'est avéré assez long), doncÀ suivre ...