OpenSceneGraph: Teknik Pemrograman Dasar

gambar

Pendahuluan


Artikel ini tidak akan terlalu fokus pada grafik seperti bagaimana aplikasi menggunakannya harus diatur, dengan mempertimbangkan spesifikasi mesin OpenSceneGraph dan perangkat lunak yang disediakannya.

Bukan rahasia lagi bahwa kunci keberhasilan setiap produk perangkat lunak adalah arsitektur yang dirancang dengan baik yang menyediakan kemampuan untuk mempertahankan dan memperluas kode tertulis. Dalam hal ini, mesin yang kami pertimbangkan berada pada level yang cukup tinggi, menyediakan pengembang dengan toolkit yang sangat luas, menyediakan konstruksi arsitektur modular yang fleksibel.

Artikel ini cukup panjang dan mencakup tinjauan berbagai alat dan teknik (pola desain, jika Anda mau) yang disediakan oleh mesin pengembang. Semua bagian artikel diberikan dengan contoh, kode yang dapat diambil di repositori saya .

1. Opsi baris perintah parsing


Dalam C / C ++, parameter baris perintah dilewatkan melalui argumen ke fungsi main (). Dalam contoh sebelumnya, kami dengan hati-hati menandai parameter ini sebagai tidak terpakai, sekarang kami akan menggunakannya untuk memberi tahu kami beberapa data saat program dimulai.

OSG memiliki alat penguraian baris perintah bawaan.

Buat contoh berikut

Contoh baris perintah
main.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(); } 


Atur parameter startup program di QtCreator



Menjalankan program untuk dieksekusi, kami mendapatkan hasilnya (model truk diambil dari OpenSceneGraph-Data yang sama )



Sekarang mari kita lihat contoh baris demi baris

 osg::ArgumentParser args(&argc, argv); 

membuat turunan dari kelas parser baris perintah osg :: ArgumentParser. Ketika dibuat, konstruktor kelas melewati argumen yang diterima oleh fungsi utama () dari sistem operasi.

 std::string filename; args.read("--model", filename); 

kami menganalisis argumen, yaitu, kami mencari kunci "–model" di antara mereka, menempatkan nilainya dalam nama file string. Dengan demikian, menggunakan kunci ini, kami mentransfer nama file dengan model tiga dimensi ke program. Selanjutnya kita memuat model ini dan menampilkannya

 osg::ref_ptr<osg::Node> root = osgDB::readNodeFile(filename); osgViewer::Viewer viewer; viewer.setSceneData(root.get()); return viewer.run(); 

Metode read () pada kelas osg :: ArgumentParser memiliki banyak kelebihan, yang memungkinkan Anda membaca tidak hanya nilai string dari baris perintah, tetapi juga bilangan bulat, angka titik mengambang, vektor, dll. Misalnya, Anda dapat membaca parameter tipe float tertentu

 float size = 0.0f; args.read("--size", size); 

Jika parameter ini tidak ditampilkan pada baris perintah, maka nilainya akan tetap seperti setelah variabel ukuran diinisialisasi.

2. Mekanisme pemberitahuan dan pencatatan


OpenSceneGraph memiliki mekanisme pemberitahuan yang memungkinkan Anda untuk menampilkan pesan debug selama proses rendering, serta diprakarsai oleh pengembang. Ini sangat membantu ketika melacak dan men-debug program. Sistem pemberitahuan OSG mendukung keluaran informasi diagnostik (kesalahan, peringatan, pemberitahuan) di tingkat inti mesin dan plug-in-nya. Pengembang dapat menampilkan pesan diagnostik selama operasi program menggunakan fungsi osg :: notify ().

Fungsi ini berfungsi sebagai aliran keluaran standar dari pustaka C ++ standar melalui overloading operator <<. Dibutuhkan tingkat pesan sebagai argumen: SELALU, FATAL, PERINGATAN, PEMBERITAHUAN, INFO, DEBUG_INFO, dan DEBUG_FP. Sebagai contoh

 osg::notify(osg::WARN) << "Some warning message" << std::endl; 

menampilkan peringatan dengan teks yang ditentukan pengguna.

Pemberitahuan OSG dapat berisi informasi penting tentang status program, ekstensi subsistem grafis komputer, kemungkinan masalah dengan mesin.

Dalam beberapa kasus, ini diperlukan untuk menampilkan data ini bukan ke konsol, tetapi untuk dapat mengarahkan output ini ke file (dalam bentuk log) atau ke antarmuka lain, termasuk widget grafis. Mesin berisi osg kelas khusus :: NotifyHandler yang menyediakan pengalihan notifikasi ke aliran output yang dibutuhkan pengembang.

Menggunakan contoh sederhana, pertimbangkan bagaimana Anda dapat mengarahkan output pemberitahuan, katakanlah, ke file log teks. Tulis kode berikut

Beri tahu contoh
main.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(); } 


Untuk mengarahkan kembali output, kita menulis kelas LogFileHandler, yang merupakan penerus dari osg :: NotifyHandler. Konstruktor dan destruktor dari kelas ini mengontrol pembukaan dan penutupan aliran keluaran _log yang dikaitkan dengan file teks. Metode notify () adalah metode kelas dasar yang serupa yang kami definisikan ulang untuk menghasilkan pemberitahuan file yang dikirim oleh OSG selama operasi melalui parameter msg.

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

Selanjutnya, di program utama, lakukan pengaturan yang diperlukan

 osg::setNotifyLevel(osg::INFO); 

atur tingkat pemberitahuan INFO, yaitu, output ke log semua informasi tentang operasi mesin, termasuk pemberitahuan saat ini tentang operasi normal.

 osg::setNotifyHandler(new LogFileHandler("../logs/log.txt")); 

pasang handler notifikasi. Selanjutnya, kami memproses argumen baris perintah di mana jalur ke model yang dimuat dilewatkan

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

Pada saat yang sama, kami menangani situasi kurangnya data pada baris perintah, menampilkan pesan dalam mode manual masuk menggunakan makro OSG_FATAL. Jalankan program dengan argumen berikut



mendapatkan output ke file log seperti ini

Contoh Log 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 


Tidak masalah bahwa saat ini informasi ini mungkin tidak berguna bagi Anda - di masa depan, kesimpulan seperti itu dapat membantu men-debug kesalahan dalam program Anda.

Secara default, OSG mengirim pesan ke std :: cout keluaran standar dan pesan kesalahan ke std :: cerr stream. Namun, dengan mengabaikan penangan notifikasi, seperti yang ditunjukkan dalam contoh, output ini dapat dialihkan ke aliran output apa pun, termasuk elemen GUI.

Ingatlah bahwa ketika mengatur pemberitahuan tingkat tinggi (misalnya, FATAL), sistem mengabaikan semua pemberitahuan dari tingkat yang lebih rendah. Misalnya, dalam kasus serupa

 osg::setNotifyLevel(osg::FATAL); . . . osg::notify(osg::WARN) << "Some message." << std::endl; 

pesan khusus tidak akan ditampilkan.

3. Intersepsi atribut geometris


Kelas osg :: Geometry mengelola seperangkat data yang menggambarkan simpul dan menampilkan mesh poligon menggunakan seperangkat primitif yang dipesan. Namun, kelas ini tidak memiliki gagasan tentang elemen topologi model seperti wajah, tepi dan hubungan di antara mereka. Nuansa ini mencegah implementasi hal-hal seperti menggerakkan wajah-wajah tertentu, misalnya saat menganimasikan model. OSG saat ini tidak mendukung fungsi ini.

Namun, mesin mengimplementasikan sejumlah fungsi yang memungkinkan Anda membaca kembali atribut geometri objek apa pun dan menggunakannya untuk memodelkan topologi mesh poligonal. Dalam C ++, functor adalah sebuah konstruk yang memungkinkan Anda untuk menggunakan objek sebagai fungsi.

Osg :: Kelas Drawable menyediakan pengembang dengan empat jenis fungsi:

  1. osg :: Drawable :: AttributeFunctor - membaca atribut simpul sebagai array dari pointer. Ini memiliki sejumlah metode virtual untuk menerapkan atribut vertex dari berbagai tipe data. Untuk menggunakan functor ini, Anda harus mendeskripsikan kelas dan menimpa satu atau lebih metode, di mana tindakan yang diperlukan oleh pengembang dilakukan


 virtual void apply( osg::Drawable::AttributeType type, unsigned int size, osg::Vec3* ptr ) { //  3-     ptr. //      } 

  1. osg :: Drawable :: ConstAttributeFunctor - versi read-only dari functor sebelumnya: pointer ke array vektor dilewatkan sebagai parameter konstan
  2. osg :: PrimitiveFunctor - meniru proses rendering objek OpenGL. Di bawah kedok rendering objek, metode functor yang ditimpa oleh pengembang disebut. Functor ini memiliki dua subkelas templat penting: osg :: TemplatePrimitiveFunctor <> dan osg :: TriangleFunctor <>. Kelas-kelas ini menerima simpul primitif sebagai parameter dan meneruskannya ke metode pengguna menggunakan operator () operator.
  3. osg :: PrimitiveIndexFunctor - melakukan tindakan yang sama dengan functor sebelumnya, tetapi menerima indeks titik primitif sebagai parameter.

Kelas-kelas yang diturunkan dari osg :: Drawable, seperti osg :: ShapeDrawable dan osg :: Geometry, memiliki metode accept () untuk menerapkan berbagai fungsi.

4. Contoh penggunaan functor primitif


Kami menggambarkan fungsionalitas yang dijelaskan menggunakan contoh pengumpulan informasi tentang wajah segitiga dan titik-titik beberapa geometri yang sebelumnya kami tentukan.

Contoh fungsi
main.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(); } 


Menghilangkan proses pembuatan geometri yang kami pertimbangkan berkali-kali, mari kita perhatikan yang berikut ini. Kami mendefinisikan struktur FaceCollector yang kami mendefinisikan ulang operator () sebagai berikut

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

Operator ini, ketika dipanggil, akan menampilkan koordinat dari tiga simpul yang dikirimkan kepadanya oleh mesin. Fungsi vec2str diperlukan untuk menerjemahkan komponen-komponen dari vektor osg :: Vec3 ke std :: string. Untuk memanggil functor, buat instance dan berikan ke objek geometri melalui metode accept ()

 osg::TriangleFunctor<FaceCollector> functor; geom->accept(functor); 

Panggilan ini, seperti yang disebutkan di atas, meniru rendering geometri, mengganti gambar itu sendiri dengan memanggil metode functor yang ditimpa. Dalam hal ini, itu akan dipanggil selama "menggambar" dari masing-masing segitiga yang membentuk geometri contoh.

Di layar kita mendapatkan geometri seperti itu



dan seperti knalpot ke konsol

 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 

Bahkan, ketika memanggil geom-> accept (...), segitiga tidak dirender, panggilan OpenGL disimulasikan, dan alih-alih data tentang simpul segitiga, rendering yang disimulasikan



Kelas osg :: TemplatePrimitiveFunctor mengumpulkan data tidak hanya tentang segitiga, tetapi juga tentang primitif OpenGL lainnya. Untuk menerapkan pemrosesan data ini, Anda harus mengganti operator berikut dalam argumen templat

 //   void operator()( const osg::Vec3&, bool ); //   void operator()( const osg::Vec3&, const osg::Vec3&, bool ); //   void operator()( const osg::Vec3&, const osg::Vec3&, const osg::Vec3&, bool ); //   void operator()( const osg::Vec3&, const osg::Vec3&, const osg::Vec3&, const osg::Vec3&, bool ); 


5. Pola Pengunjung


Pola pengunjung digunakan untuk mengakses operasi untuk mengubah elemen grafik adegan tanpa mengubah kelas elemen ini. Kelas pengunjung mengimplementasikan semua fungsi virtual yang relevan untuk menerapkannya ke berbagai jenis elemen melalui mekanisme pengiriman ganda. Dengan menggunakan mekanisme ini, pengembang dapat membuat instance pengunjungnya sendiri dengan mengimplementasikan fungsionalitas yang dibutuhkannya dengan bantuan operator khusus dan mengikat pengunjung ke berbagai jenis elemen grafik adegan dengan cepat, tanpa mengubah fungsionalitas elemen itu sendiri. Ini adalah cara yang bagus untuk memperluas fungsionalitas suatu elemen tanpa mendefinisikan subclass dari elemen-elemen ini.

Untuk mengimplementasikan mekanisme ini dalam OSG, kelas osg :: NodeVisitor didefinisikan. Kelas yang diwarisi dari osg :: NodeVisitor bergerak di sekitar grafik adegan, mengunjungi setiap node dan menerapkan operasi yang ditentukan oleh pengembang. Ini adalah kelas utama yang digunakan untuk campur tangan dalam proses memperbarui node dan kliping node tak terlihat, serta menerapkan beberapa operasi lain yang terkait dengan memodifikasi geometri adegan adegan, seperti osgUtil :: SmoothingVisitor, osgUtil :: Simplifier dan osgUtil :: TriStripVisitor.

Untuk mensubclass pengunjung, kita harus mengganti satu atau lebih metode apply () virtual berlebih yang disediakan oleh kelas dasar osg :: NodeVisitor. Sebagian besar tipe simpul OSG utama memiliki metode ini. Pengunjung akan secara otomatis memanggil metode apply () untuk setiap node yang dikunjungi ketika melintasi grafik adegan adegan. Pengembang mengabaikan metode apply () untuk setiap jenis simpul yang ia butuhkan.

Dalam implementasi metode apply (), pengembang, pada saat yang tepat, harus memanggil metode traverse () dari kelas dasar osg :: NodeVisitor. Ini memulai transisi pengunjung ke simpul berikutnya, baik anak atau tetangga di tingkat hierarki, jika simpul saat ini tidak memiliki simpul anak yang dapat dibuat transisi. Tidak adanya panggilan untuk melintasi () berarti menghentikan traversal grafik adegan dan sisa grafik adegan diabaikan.

Kelebihan metode apply () memiliki format terpadu

 virtual void apply( osg::Node& ); virtual void apply( osg::Geode& ); virtual void apply( osg::Group& ); virtual void apply( osg::Transform& ); 

Untuk memotong subgraph dari node saat ini untuk objek pengunjung, Anda harus mengatur mode crawl, misalnya,

 ExampleVisitor visitor; visitor->setTraversalMode( osg::NodeVisitor::TRAVERSE_ALL_CHILDREN ); node->accept( visitor ); 

Mode Bypass diatur oleh beberapa enumerator

  1. TRAVERSE_ALL_CHILDREN - bergerak melalui semua node anak.
  2. TRAVERSE_PARENTS - kembali dari simpul saat ini, tidak mencapai simpul akar
  3. TRAVERSE_ACTIVE_CHILDREN - memotong node yang aktif secara eksklusif, yaitu mereka yang visibilitasnya diaktifkan melalui osg :: Switch node.


6. Analisis struktur cessna yang terbakar


Pengembang selalu dapat menganalisis bagian grafik adegan yang dihasilkan oleh model yang diambil dari file.

Contoh fungsi
main.h

 #ifndef MAIN_H #define MAIN_H #include <osgDB/ReadFile> #include <osgViewer/Viewer> #include <iostream> #endif 

main.cpp

 #include "main.h" //------------------------------------------------------------------------------ // //------------------------------------------------------------------------------ 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; }; //------------------------------------------------------------------------------ // //------------------------------------------------------------------------------ void InfoVisitor::apply(osg::Node &node) { std::cout << spaces() << node.libraryName() << "::" << node.className() << std::endl; _level++; traverse(node); _level--; } //------------------------------------------------------------------------------ // //------------------------------------------------------------------------------ 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--; } //------------------------------------------------------------------------------ // //------------------------------------------------------------------------------ int main(int argc, char *argv[]) { 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; } InfoVisitor infoVisitor; root->accept(infoVisitor); osgViewer::Viewer viewer; viewer.setSceneData(root.get()); return viewer.run(); } 


Kami membuat kelas InfoVisitor, mewarisinya dari 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; }; 

Properti _level yang dilindungi akan menunjuk ke tingkat grafik adegan di mana kelas pengunjung kami saat ini berada. Di konstruktor, inisialisasi penghitung level dan atur mode node traversal - untuk mem-bypass semua node anak.

Sekarang mendefinisikan kembali metode apply () untuk node

 void InfoVisitor::apply(osg::Node &node) { std::cout << spaces() << node.libraryName() << "::" << node.className() << std::endl; _level++; traverse(node); _level--; } 

Di sini kita akan menampilkan tipe node saat ini. Metode libraryName () untuk node menampilkan nama perpustakaan OSG di mana node ini diterapkan, dan metode className menampilkan nama kelas node. Metode ini diimplementasikan melalui penggunaan makro dalam kode pustaka OSG.

 std::cout << spaces() << node.libraryName() << "::" << node.className() << std::endl; 

Setelah itu, kami meningkatkan penghitung tingkat grafik dan memanggil metode traverse (), memulai transisi ke tingkat yang lebih tinggi, ke simpul anak. Setelah kembali dari traverse (), kami kembali mengurangi nilai penghitung. Sangat mudah untuk menebak bahwa traverse () memulai panggilan berulang ke metode apply (), traverse berulang () sudah untuk subgraph mulai dari node saat ini. Kami mendapatkan eksekusi pengunjung rekursif sampai kami mencapai titik akhir dari grafik adegan.

Untuk simpul akhir dari tipe osg :: Geode, kelebihannya dari metode apply () diganti

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

dengan kode kerja yang sama, kecuali bahwa kami menampilkan data pada semua objek geometrik yang melekat pada simpul geometrik saat ini

 for (unsigned int i = 0; i < geode.getNumDrawables(); ++i) { osg::Drawable *drawable = geode.getDrawable(i); std::cout << spaces() << drawable->libraryName() << "::" << drawable->className() << std::endl; } 

Dalam fungsi utama (), kami memproses argumen baris perintah di mana kami melewati daftar model yang dimuat ke dalam adegan dan membentuk adegan

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

Pada saat yang sama, kami memproses kesalahan terkait dengan tidak adanya nama file model pada baris perintah. Sekarang kita membuat kelas pengunjung dan meneruskannya ke grafik adegan untuk dieksekusi

 InfoVisitor infoVisitor; root->accept(infoVisitor); 

Berikutnya adalah langkah-langkah untuk meluncurkan penampil, yang telah kami lakukan berkali-kali. Setelah memulai program dengan parameter

 $ visitor ../data/cessnafire.osg 

kita akan melihat output berikut ke konsol

 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 

Bahkan, kami mendapat pohon lengkap dari adegan yang dimuat. Maaf, di mana ada begitu banyak node? Semuanya sangat sederhana - model format * .osg sendiri adalah wadah yang menyimpan tidak hanya geometri model, tetapi juga informasi lain tentang strukturnya dalam bentuk subgraf adegan OSG. Geometri model, transformasi, efek partikel yang menyadari asap dan api adalah semua simpul dari grafik adegan OSG.Adegan apa pun dapat diunduh dari * .osg atau dibongkar dari pemirsa ke format * .osg.

Ini adalah contoh sederhana menerapkan mekanisme pengunjung. Bahkan, di dalam pengunjung, Anda dapat melakukan banyak operasi untuk memodifikasi node ketika program sedang berjalan.

7. Mengontrol perilaku node dalam grafik adegan dengan mengganti metode traverse ()


Cara penting untuk bekerja dengan OSG adalah mengganti metode traverse (). Metode ini disebut setiap kali bingkai digambar. Mereka menerima parameter tipe osg :: NodeVisitor & yang melaporkan bagian mana dari grafik adegan yang saat ini dilakukan (memperbarui, memproses acara, atau kliping). Sebagian besar host OSG mengganti metode ini untuk mengimplementasikan fungsionalitasnya.

Harus diingat bahwa menimpa metode traverse () bisa berbahaya, karena memengaruhi proses melintasi grafik adegan dan dapat menyebabkan tampilan adegan yang salah. Ini juga merepotkan jika Anda ingin menambahkan fungsionalitas baru ke beberapa jenis node. Dalam hal ini, callback node digunakan, percakapan tentang yang akan sedikit lebih rendah.

, osg::Switch , . , , , .

animswitch
main.h

 #ifndef MAIN_H #define MAIN_H #include <osg/Switch> #include <osgDB/ReadFile> #include <osgViewer/Viewer> #endif 

main.cpp

 #include "main.h" //------------------------------------------------------------------------------ // //------------------------------------------------------------------------------ class AnimatingSwitch : public osg::Switch { public: AnimatingSwitch() : osg::Switch(), _count(0) {} AnimatingSwitch(const AnimatingSwitch &copy, const osg::CopyOp &copyop = 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); } //------------------------------------------------------------------------------ // //------------------------------------------------------------------------------ int main(int argc, char *argv[]) { (void) argc; (void) argv; osg::ref_ptr<osg::Node> model1 = osgDB::readNodeFile("../data/cessna.osg"); osg::ref_ptr<osg::Node> model2 = osgDB::readNodeFile("../data/cessnafire.osg"); osg::ref_ptr<AnimatingSwitch> root = new AnimatingSwitch; root->addChild(model1.get(), true); root->addChild(model2.get(), false); osgViewer::Viewer viewer; viewer.setSceneData(root.get()); return viewer.run(); } 


. AnimatingSwitch, osg::Switch.

 class AnimatingSwitch : public osg::Switch { public: AnimatingSwitch() : osg::Switch(), _count(0) {} AnimatingSwitch(const AnimatingSwitch &copy, const osg::CopyOp &copyop = 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); } 

-

 AnimatingSwitch() : osg::Switch(), _count(0) {} 

, OSG

 AnimatingSwitch(const AnimatingSwitch &copy, const osg::CopyOp &copyop = osg::CopyOp::SHALLOW_COPY) : osg::Switch(copy, copyop), _count(copy._count) {} 

: , osg::CopyOp, .

 META_Node(osg, AnimatingSwitch); 

Ini adalah makro yang membentuk struktur yang diperlukan untuk keturunan kelas yang berasal dari osg :: Node. Sampai kita mementingkan makro ini, penting bahwa itu harus ada ketika mewarisi dari osg :: Beralih ketika mendefinisikan semua kelas turunan. Kelas berisi field yang dilindungi _count - penghitung yang didasarkan pada mana kita beralih. Kami menerapkan pengalihan ketika mengganti metode traverse ()

 void AnimatingSwitch::traverse(osg::NodeVisitor &nv) { if (!((++_count) % 60) ) { setValue(0, !getValue(0)); setValue(1, !getValue(1)); } osg::Switch::traverse(nv); } 

Mengganti status tampilan node akan terjadi setiap kali nilai penghitung (menambah setiap panggilan metode) adalah kelipatan 60. Kami mengkompilasi contoh dan menjalankannya



Karena metode traverse () secara konstan didefinisikan ulang untuk berbagai jenis node, metode ini harus menyediakan mekanisme untuk memperoleh matriks transformasi dan membuat status untuk digunakan lebih lanjut oleh algoritma kelebihan beban mereka. Parameter input osg :: NodeVisitor adalah kunci untuk berbagai operasi dengan node. Secara khusus, ini menunjukkan jenis traversal grafik adegan saat ini, seperti memperbarui, memproses peristiwa, dan memotong wajah yang tidak terlihat. Dua yang pertama terkait dengan panggilan balik simpul dan akan dipertimbangkan saat mempelajari animasi.

Passing klip dapat diidentifikasi dengan mengonversi objek osg :: NodeVisitor ke osg :: CullVisitor

 osgUtil::CullVisitor *cv = dynamic_cast<osgUtil::CullVisitor *>(&nv); if (cv) { ///  - ,     } 


8. Mekanisme panggilan balik


, . , . , .

. , osg::NodeCallback , osg::Drawable::UpdateCallback, osg::Drawable::EventCallback osg::Drawable:CullCallback β€” , .

osg::NodeCallback operator(), . , , setUpdateCallback() addUpdateCallback(). operator() .

, OSG

osg::NodeCallbackoperator()osg::Node::setUpdateCallback()
osg::NodeCallbackoperator()osg::Node::setEventCallback()
osg::NodeCallbackoperator()osg::Node::setCullCallback()
osg::Drawable::UpdateCallbackupdate()osg::Drawable::setUpdateCallback()
osg::Drawable::EventCallbackevent()osg::Drawable::setEventCallback()
osg::Drawable::CullCallbackcull()osg::Drawable::setCullCallback()
osg::StateAttributeCallbackoperator()osg::StateAttribute::setUpdateCallback()
osg::StateAttributeCallbackoperator()osg::StateAttribute::setEventCallback()
osg::Uniform::Callbackoperator()osg::Uniform::setUpdateCallback()
osg::Uniform::Callbackoperator()osg::Uniform::setEvevtCallback()
osg::Camera::DrawCallbackoperator()osg::Camera::PreDrawCallback()
osg::Camera::DrawCallbackoperator()osg::Camera::PostDrawCallback()


9. osg::Switch


Sedikit lebih tinggi, kami menulis contoh dengan mengganti dua model pesawat. Sekarang kita akan mengulangi contoh ini, tetapi kita akan melakukan semuanya dengan benar menggunakan mekanisme panggilan balik OSG.

Panggil kembali dengan contoh
main.h

 #ifndef MAIN_H #define MAIN_H #include <osg/Switch> #include <osgDB/ReadFile> #include <osgViewer/Viewer> #endif 

main.cpp

 #include "main.h" //------------------------------------------------------------------------------ // //------------------------------------------------------------------------------ class SwitchingCallback : public osg::NodeCallback { public: SwitchingCallback() : _count(0) {} virtual void operator()(osg::Node *node, osg::NodeVisitor *nv); protected: unsigned int _count; }; //------------------------------------------------------------------------------ // //------------------------------------------------------------------------------ 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); } //------------------------------------------------------------------------------ // //------------------------------------------------------------------------------ int main(int argc, char *argv[]) { (void) argc; (void) argv; osg::ref_ptr<osg::Node> model1 = osgDB::readNodeFile("../data/cessna.osg"); osg::ref_ptr<osg::Node> model2 = osgDB::readNodeFile("../data/cessnafire.osg"); osg::ref_ptr<osg::Switch> root = new osg::Switch; root->addChild(model1, true); root->addChild(model2, false); root->setUpdateCallback( new SwitchingCallback ); osgViewer::Viewer viewer; viewer.setSceneData(root.get()); return viewer.run(); } 


Anda harus membuat kelas yang diwarisi dari osg :: NodeCallback, yang mengontrol osg :: Switch node

 class SwitchingCallback : public osg::NodeCallback { public: SwitchingCallback() : _count(0) {} virtual void operator()(osg::Node *node, osg::NodeVisitor *nv); protected: unsigned int _count; }; 

Penghitung _count akan mengontrol peralihan osg :: Alihkan node dari memetakan satu simpul anak ke lainnya, tergantung pada nilainya. Di konstruktor, kami menginisialisasi penghitung, dan mendefinisikan kembali metode operator virtual ()

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

Node tempat panggilan berfungsi diteruskan ke parameter node. Karena kita tahu pasti bahwa ini akan menjadi simpul dari tipe osg :: Switch, kita melakukan cast statis dari pointer ke node ke pointer ke node switch

 osg::Switch *switchNode = static_cast<osg::Switch *>(node); 

Kami akan mengganti node anak yang ditampilkan dengan nilai valid dari pointer ini, dan ketika nilai counter adalah kelipatan 60

 if ( !((++_count) % 60) && switchNode ) { switchNode->setValue(0, !switchNode->getValue(0)); switchNode->setValue(1, !switchNode->getValue(0)); } 

Jangan lupa untuk memanggil metode traverse () untuk melanjutkan traversal rekursif dari grafik adegan

 traverse(node, nv); 

Sisa kode program adalah sepele, kecuali untuk baris

 root->setUpdateCallback( new SwitchingCallback ); 

tempat kami menetapkan panggilan balik yang kami buat ke simpul akar dari tipe osg :: Switch. Program bekerja mirip dengan contoh sebelumnya



traverse() : - osg::NodeVisitor, .

traverse(), . , traverse() .

addUpdateCallback(). setUpdateCallback() . .

Kesimpulan


Kami memeriksa teknik dasar yang digunakan dalam mengembangkan aplikasi menggunakan mesin grafis OpenSceneGraph. Namun, ini jauh dari semua poin yang ingin saya sentuh (terlepas dari kenyataan bahwa artikel tersebut ternyata cukup panjang), oleh karena itu

Dilanjutkan ...

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


All Articles