OpenSceneGraph: Sistem Plugin

gambar

Pendahuluan


Di suatu tempat dalam pelajaran sebelumnya, sudah dikatakan bahwa OSG mendukung memuat berbagai jenis sumber daya seperti gambar raster, model 3D dari berbagai format, atau, misalnya, font melalui sistem plug-in sendiri. Plugin OSG adalah komponen terpisah yang memperluas fungsi mesin dan memiliki antarmuka standar dalam OSG. Plugin ini diimplementasikan sebagai pustaka bersama dinamis (dll pada Windows, begitu juga Linux, dll.). Nama-nama pustaka plugin sesuai dengan konvensi tertentu

osgdb_< >.dll 

artinya, nama plugin selalu berisi awalan osgdb_. Ekstensi file memberi tahu mesin tentang plug-in mana yang harus digunakan untuk mengunduh file dengan ekstensi ini. Misalnya, ketika kita menulis fungsi dalam kode

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

mesin melihat ekstensi osg dan memuat plugin bernama osgdb_osg.dll (atau osgdb_osg.so dalam kasus Linux). Kode plugin melakukan semua pekerjaan kotor dengan mengembalikan kita sebuah pointer ke sebuah node yang menggambarkan model cessna. Demikian pula, mencoba memuat gambar PNG

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

akan menyebabkan plugin osgdb_png.dll dimuat, yang mengimplementasikan algoritma untuk membaca data dari gambar PNG dan menempatkan data ini dalam objek bertipe osg :: Image.

Semua operasi yang bekerja dengan sumber daya eksternal dilaksanakan oleh fungsi pustaka osgDB, yang dengannya kami selalu menautkan program dari contoh ke contoh. Perpustakaan ini bergantung pada sistem plugin OSG. Hingga saat ini, paket OSG mencakup banyak plug-in yang berfungsi dengan sebagian besar format gambar, model 3D, dan font yang digunakan dalam praktiknya. Plugin menyediakan data membaca (impor) dari format tertentu, dan, dalam kebanyakan kasus, menulis data ke file dengan format yang diperlukan (ekspor). Utilitas osgconv, khususnya, memungkinkan Anda untuk mengkonversi data dari satu format ke format lainnya, misalnya, sistem plug-in.

 $ osgconv cessna.osg cessna.3ds 

dengan mudah dan alami mengubah model cessna osg ke format 3DS, yang kemudian dapat diimpor ke editor 3D, misalnya, ke Blender (omong-omong, ada ekstensi untuk bekerja dengan osg langsung untuk Blender)



Ada daftar resmi plugin OSG standar dengan deskripsi tujuan mereka, tetapi ini panjang dan saya terlalu malas untuk membawanya ke sini. Lebih mudah untuk melihat jalur instalasi perpustakaan di folder bin / ospPlugins-xyz, di mana x, y, z adalah nomor versi OSG. Dari nama file plugin, mudah untuk memahami format apa yang diprosesnya.

Jika OSG dikompilasi oleh kompiler MinGW, awalan mingw_ tambahan ditambahkan ke nama standar plugin, yaitu, nama akan terlihat seperti ini

 mingw_osgdb_< >.dll 

Versi plugin yang dikompilasi dalam konfigurasi DEBUG juga dilengkapi dengan akhiran d di akhir nama, yaitu format akan menjadi

 osgdb_< >d.dll 

atau

 mingw_osgdb_< >d.dll 

saat merakit MinGW.

1. Plugin pseudo-loader


Beberapa plugin OSG menjalankan fungsi yang disebut pseudo-loader - ini berarti bahwa mereka tidak terikat pada ekstensi file tertentu, tetapi dengan menambahkan akhiran ke akhir nama file, Anda dapat menentukan plug-in mana yang harus digunakan untuk mengunduh file ini, misalnya

 $ osgviewer worldmap.shp.ogr 

Dalam hal ini, nama asli file pada disk adalah worldmap.shp - file ini menyimpan peta dunia dalam format ESRI shapefile. Suffix .ogr memberi tahu perpustakaan osgDB untuk menggunakan plugin osgdb_ogr untuk memuat file ini; jika tidak, plugin osgdb_shp akan digunakan.

Contoh bagus lainnya adalah plugin osgdb_ffmpeg. Pustaka FFmpeg mendukung lebih dari 100 codec yang berbeda. Untuk membacanya, kita cukup menambahkan suffix .ffmpeg setelah nama file media.

Selain itu, beberapa pseudo-loader memungkinkan kita untuk melewati akhiran sejumlah parameter yang mempengaruhi keadaan objek yang dimuat, dan kita sudah menemukan ini dalam contoh dengan animasi

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

Baris 0.90 menunjukkan ke plugin osgdb_osg parameter orientasi awal model yang dimuat. Beberapa pseudo-loader membutuhkan parameter yang benar-benar spesifik untuk berfungsi.

2. API untuk mengembangkan plugin pihak ketiga


Sangat logis jika, setelah semua pembacaan, Anda memiliki gagasan bahwa mungkin tidak akan sulit untuk menulis plug-in Anda sendiri untuk OSG, yang akan memungkinkan Anda untuk mengimpor format model atau gambar 3D yang tidak standar. Dan ini adalah pemikiran yang benar! Mekanisme plugin hanya dirancang untuk memperluas fungsionalitas mesin tanpa mengubah OSG itu sendiri. Untuk memahami prinsip dasar penulisan plugin, mari kita coba menerapkan contoh sederhana.

Pengembangan plugin adalah untuk memperluas antarmuka baca / tulis virtual yang disediakan oleh OSG. Fungsi ini disediakan oleh kelas virtual osgDB :: ReaderWriter. Kelas ini menyediakan sejumlah metode virtual yang didefinisikan ulang oleh pengembang plugin.
MetodeDeskripsi
mendukung Ekstensi ()Ini menerima dua parameter string: ekstensi file dan deskripsi. Metode ini selalu disebut dalam konstruktor dari subclass.
acceptsExtension ()Mengembalikan nilai true jika ekstensi yang diteruskan sebagai argumen didukung oleh plugin
fileExists ()Memungkinkan Anda menentukan apakah file yang diberikan ada (path dilewatkan sebagai parameter) pada disk (mengembalikan true jika berhasil)
readNode ()Menerima nama file dan opsi sebagai objek osgDB :: Option. Fungsi untuk membaca data dari file diimplementasikan oleh pengembang
writeNode ()Menerima nama simpul, nama file yang diinginkan, dan opsi. Fungsi penulisan data ke disk diimplementasikan oleh pengembang
readImage ()Membaca data bitmap disk
writeImage ()Menulis bitmap ke disk

Implementasi metode readNode () dapat dijelaskan dengan kode berikut

 osgDB::ReaderWriter::ReadResult readNode( const std::string &file, const osgDB::Options *options) const { //         bool recognizableExtension = ...; bool fileExists = ...; if (!recognizableExtension) return ReadResult::FILE_NOT_HANDLED; if (!fileExists) return ReadResult::FILE_NOT_FOUND; //          osg::Node *root = ...; //       -     . //    -      bool errorInParsing = ...; if (errorInParsing) return ReadResult::ERROR_IN_READING_FILE; return root; } 

Agak mengejutkan bahwa alih-alih sebuah penunjuk ke simpul grafik adegan, metode ini mengembalikan tipe osgDB :: ReaderWriter :: ReadResult. Tipe ini adalah objek hasil baca, dan dapat digunakan sebagai wadah simpul, gambar, pencacah status (misalnya, FILE_NOT_FOUND), objek khusus lain, atau bahkan sebagai string pesan kesalahan. Ini memiliki banyak konstruktor implisit untuk mengimplementasikan fungsi yang dijelaskan.

Kelas bermanfaat lainnya adalah osgDB :: Options. Itu dapat memungkinkan Anda untuk mengatur atau mendapatkan serangkaian opsi pemuatan menggunakan metode setOptionString () dan getOptionString (). Melewati string ini ke konstruktor kelas ini sebagai argumen juga diperbolehkan.

Pengembang dapat mengontrol perilaku plugin dengan mengatur pengaturan dalam string parameter yang dilewatkan saat memuat objek, misalnya, dengan cara ini

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

3. Pemrosesan aliran data dalam plugin OSG


Kelas dasar osgDB :: ReaderWriter mencakup serangkaian metode yang memproses data aliran input / output yang disediakan oleh pustaka C ++ standar. Satu-satunya perbedaan antara metode baca / tulis ini dan yang dibahas di atas adalah bahwa alih-alih nama file, mereka menerima std :: istream & input stream atau std :: ostream & output stream. Menggunakan file I / O stream selalu lebih baik daripada menggunakan nama file. Untuk melakukan operasi pembacaan file, kita dapat menggunakan desain antarmuka berikut:

 osgDB::ReaderWriter::ReadResult readNode( const std::string &file, const osgDB::Options *options) const { ... osgDB::ifstream stream(file.c_str(), std::ios::binary); if (!stream) return ReadResult::ERROR_IN_READING_FILE; return readNode(stream, options); } ... osgDB::ReaderWriter::ReadResult readNode( std::istream &stream, const osgDB::Options *options) const { //         osg::Node *root = ...; return root; } 

Setelah mengimplementasikan plugin, kita dapat menggunakan fungsi standar osgDB :: readNodeFile () dan osgDB :: readImageFile () untuk memuat model dan gambar, cukup dengan menentukan path file. OSG akan menemukan dan mengunduh plugin yang kami tulis.

4. Kami menulis plugin kami sendiri



Jadi, tidak ada yang mengganggu kita untuk menghasilkan format kita sendiri untuk menyimpan data pada geometri tiga dimensi, dan kita akan memunculkannya

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 

Di sini, di awal file adalah daftar simpul dengan koordinatnya. Indeks vertex berjalan berurutan, mulai dari nol. Setelah daftar simpul muncul daftar wajah. Setiap wajah ditentukan oleh daftar indeks titik dari mana ia dibentuk. Ternyata tidak ada yang rumit. Tugasnya adalah membaca file ini dari disk dan membentuk geometri tiga dimensi atas dasar.

5. Pengaturan proyek plugin: membangun fitur skrip


Jika sebelum kita membangun aplikasi, sekarang kita harus menulis perpustakaan yang dinamis, dan bukan hanya perpustakaan, tetapi plug-in OSG yang memenuhi persyaratan tertentu. Kami akan mulai memenuhi persyaratan ini dengan skrip build proyek yang akan terlihat seperti ini

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) 

Kami akan menganalisis nuansa individual secara lebih rinci

 TEMPLATE = lib 

berarti kita akan membangun perpustakaan. Untuk mencegah pembuatan tautan simbolik dengan bantuan masalah konflik versi pustaka yang diselesaikan dalam sistem * nix, kami mengindikasikan ke sistem build bahwa pustaka ini akan menjadi plug-in, artinya, itu akan dimuat ke dalam memori "saat itu juga"

 CONFIG += plugin 

Selanjutnya, kami mengecualikan pembuatan awalan lib, yang ditambahkan saat menggunakan kompiler keluarga gcc dan diperhitungkan oleh lingkungan runtime saat memuat pustaka

 CONFIG += no_plugin_name_prefix 

Tetapkan nama file perpustakaan

 TARGET = osgdb_pmd 

di mana pmd adalah ekstensi file format model 3D yang kami temukan. Selanjutnya, kita harus menunjukkan bahwa dalam kasus perakitan MinGW, awalan mingw_ perlu ditambahkan ke namanya

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

Tentukan jalur pembuatan pustaka: untuk Windows

 DESTDIR = $$(OSG_PLUGINS_PATH) 

untuk linux

 DESTDIR = /usr/lib/osgPlugins-3.7.0 

Untuk Linux, dengan indikasi jalan (yang tidak diragukan lagi adalah penopang, tapi saya belum menemukan solusi lain), kami memberikan hak untuk menulis ke folder yang ditentukan dengan plugin OSG dari pengguna biasa

 # chmod 666 /usr/lib/osgPlugins-3.7.0 

Semua pengaturan build lainnya mirip dengan yang digunakan dalam perakitan aplikasi sampel sebelumnya.

6. Pengaturan proyek plugin: fitur mode debug


Karena proyek ini adalah perpustakaan yang dinamis, harus ada program yang memuat perpustakaan ini dalam proses pelaksanaannya. Ini bisa berupa aplikasi apa pun yang menggunakan OSG dan di mana fungsinya akan dipanggil

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

Dalam hal ini, plugin kami akan dimuat. Agar Anda tidak menulis program semacam itu sendiri, kami akan menggunakan solusi yang sudah jadi - penampil osgviewer standar, yang termasuk dalam paket pengiriman mesin. Jika di konsol jalankan

 $ osgviewer piramide.pmd 

maka itu juga akan memicu plugin. Dalam pengaturan peluncuran proyek, tentukan path ke osgviewerd, sebagai direktori kerja, tentukan direktori tempat file piramide.pmd berada, dan tentukan file yang sama dalam opsi baris perintah osgviewer



Sekarang kita dapat menjalankan plugin dan men-debug-nya langsung dari QtCreator IDE.

6. Kami menerapkan kerangka kerja plugin


Contoh ini sampai batas tertentu menggeneralisasikan pengetahuan yang telah kami terima tentang OSG dari pelajaran sebelumnya. Saat menulis plugin, kita harus

  1. Pilih struktur data untuk menyimpan informasi geometri model yang dibaca dari file model
  2. Baca dan parsing (parse) file data model
  3. Mengkonfigurasi osg :: Drawable geometric object berdasarkan data yang dibaca dari file
  4. Buat subgraph adegan untuk model yang dimuat

Jadi, berdasarkan tradisi, saya akan memberikan seluruh kode sumber plugin

Plugin Osgdb_pmd
main.h

 #ifndef MAIN_H #define MAIN_H #include <osg/Geometry> #include <osg/Geode> #include <osgDB/FileNameUtils> #include <osgDB/FileUtils> #include <osgDB/Registry> #include <osgUtil/SmoothingVisitor> //------------------------------------------------------------------------------ // //------------------------------------------------------------------------------ struct face_t { std::vector<unsigned int> indices; }; //------------------------------------------------------------------------------ // //------------------------------------------------------------------------------ struct pmd_mesh_t { osg::ref_ptr<osg::Vec3Array> vertices; osg::ref_ptr<osg::Vec3Array> normals; std::vector<face_t> faces; pmd_mesh_t() : vertices(new osg::Vec3Array) , normals(new osg::Vec3Array) { } osg::Vec3 calcFaceNormal(const face_t &face) const { osg::Vec3 v0 = (*vertices)[face.indices[0]]; osg::Vec3 v1 = (*vertices)[face.indices[1]]; osg::Vec3 v2 = (*vertices)[face.indices[2]]; osg::Vec3 n = (v1 - v0) ^ (v2 - v0); return n * (1 / n.length()); } }; //------------------------------------------------------------------------------ // //------------------------------------------------------------------------------ class ReaderWriterPMD : public osgDB::ReaderWriter { public: ReaderWriterPMD(); virtual ReadResult readNode(const std::string &filename, const osgDB::Options *options) const; virtual ReadResult readNode(std::istream &stream, const osgDB::Options *options) const; private: pmd_mesh_t parsePMD(std::istream &stream) const; std::vector<std::string> parseLine(const std::string &line) const; }; #endif 

main.cpp

 #include "main.h" //------------------------------------------------------------------------------ // //------------------------------------------------------------------------------ ReaderWriterPMD::ReaderWriterPMD() { supportsExtension("pmd", "PMD model file"); } //------------------------------------------------------------------------------ // //------------------------------------------------------------------------------ osgDB::ReaderWriter::ReadResult ReaderWriterPMD::readNode( const std::string &filename, const osgDB::Options *options) const { std::string ext = osgDB::getLowerCaseFileExtension(filename); if (!acceptsExtension(ext)) return ReadResult::FILE_NOT_HANDLED; std::string fileName = osgDB::findDataFile(filename, options); if (fileName.empty()) return ReadResult::FILE_NOT_FOUND; std::ifstream stream(fileName.c_str(), std::ios::in); if (!stream) return ReadResult::ERROR_IN_READING_FILE; return readNode(stream, options); } //------------------------------------------------------------------------------ // //------------------------------------------------------------------------------ osgDB::ReaderWriter::ReadResult ReaderWriterPMD::readNode( std::istream &stream, const osgDB::Options *options) const { (void) options; pmd_mesh_t mesh = parsePMD(stream); osg::ref_ptr<osg::Geometry> geom = new osg::Geometry; geom->setVertexArray(mesh.vertices.get()); for (size_t i = 0; i < mesh.faces.size(); ++i) { osg::ref_ptr<osg::DrawElementsUInt> polygon = new osg::DrawElementsUInt(osg::PrimitiveSet::POLYGON, 0); for (size_t j = 0; j < mesh.faces[i].indices.size(); ++j) polygon->push_back(mesh.faces[i].indices[j]); geom->addPrimitiveSet(polygon.get()); } geom->setNormalArray(mesh.normals.get()); geom->setNormalBinding(osg::Geometry::BIND_PER_PRIMITIVE_SET); osg::ref_ptr<osg::Geode> geode = new osg::Geode; geode->addDrawable(geom.get()); return geode.release(); } //------------------------------------------------------------------------------ // //------------------------------------------------------------------------------ pmd_mesh_t ReaderWriterPMD::parsePMD(std::istream &stream) const { pmd_mesh_t mesh; while (!stream.eof()) { std::string line; std::getline(stream, line); std::vector<std::string> tokens = parseLine(line); if (tokens[0] == "vertex") { osg::Vec3 point; std::istringstream iss(tokens[1]); iss >> point.x() >> point.y() >> point.z(); mesh.vertices->push_back(point); } if (tokens[0] == "face") { unsigned int idx = 0; std::istringstream iss(tokens[1]); face_t face; while (!iss.eof()) { iss >> idx; face.indices.push_back(idx); } mesh.faces.push_back(face); mesh.normals->push_back(mesh.calcFaceNormal(face)); } } return mesh; } //------------------------------------------------------------------------------ // //------------------------------------------------------------------------------ std::string delete_symbol(const std::string &str, char symbol) { std::string tmp = str; tmp.erase(std::remove(tmp.begin(), tmp.end(), symbol), tmp.end()); return tmp; } //------------------------------------------------------------------------------ // //------------------------------------------------------------------------------ std::vector<std::string> ReaderWriterPMD::parseLine(const std::string &line) const { std::vector<std::string> tokens; std::string tmp = delete_symbol(line, '\r'); size_t pos = 0; std::string token; while ( (pos = tmp.find(':')) != std::string::npos ) { token = tmp.substr(0, pos); tmp.erase(0, pos + 1); if (!token.empty()) tokens.push_back(token); } tokens.push_back(tmp); return tokens; } REGISTER_OSGPLUGIN( pmd, ReaderWriterPMD ) 


Pertama, mari kita perhatikan struktur untuk menyimpan data geometri.

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

- menjelaskan wajah yang ditentukan oleh daftar indeks dari simpul-simpul yang dimiliki wajah ini. Model secara keseluruhan akan dijelaskan oleh struktur seperti itu

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

Struktur terdiri dari variabel anggota untuk menyimpan data: simpul - untuk menyimpan array simpul objek geometrik; normals - array normals ke wajah objek; wajah - daftar wajah objek. Konstruktor struktur segera menginisialisasi pointer pintar

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

Selain itu, struktur berisi metode yang memungkinkan Anda untuk menghitung vektor normal ke wajah calcFaceNormal () sebagai parameter yang mengambil struktur yang menggambarkan wajah. Kami tidak akan membahas detail implementasi metode ini, kami akan menganalisisnya sedikit nanti.

Jadi, kami memutuskan pada struktur di mana kami akan menyimpan data geometri. Sekarang mari kita menulis kerangka kerja plugin kita, yaitu, kita menerapkan kelas pewaris 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; }; 

Seperti yang direkomendasikan dalam deskripsi API untuk mengembangkan plugin, di kelas ini kami mendefinisikan kembali metode membaca data dari file dan mengubahnya menjadi subgraph dari adegan tersebut. Metode readNode () melakukan dua overload - satu menerima nama file sebagai input, yang lain menerima input standar. Konstruktor kelas mendefinisikan ekstensi file yang didukung oleh plugin

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

Kelebihan pertama dari metode readNode () menganalisis kebenaran nama file dan path untuk itu, mengaitkan aliran input standar dengan file, dan memanggil kelebihan kedua, yang melakukan pekerjaan utama

 osgDB::ReaderWriter::ReadResult ReaderWriterPMD::readNode( const std::string &filename, const osgDB::Options *options) const { //       std::string ext = osgDB::getLowerCaseFileExtension(filename); // ,      if (!acceptsExtension(ext)) return ReadResult::FILE_NOT_HANDLED; // ,       std::string fileName = osgDB::findDataFile(filename, options); if (fileName.empty()) return ReadResult::FILE_NOT_FOUND; //      std::ifstream stream(fileName.c_str(), std::ios::in); if (!stream) return ReadResult::ERROR_IN_READING_FILE; //      readNode() return readNode(stream, options); } 

Pada overload kedua, kami mengimplementasikan algoritma pembuatan objek untuk OSG

 osgDB::ReaderWriter::ReadResult ReaderWriterPMD::readNode( std::istream &stream, const osgDB::Options *options) const { (void) options; //   *.pmd       pmd_mesh_t mesh = parsePMD(stream); //    osg::ref_ptr<osg::Geometry> geom = new osg::Geometry; //    geom->setVertexArray(mesh.vertices.get()); //    for (size_t i = 0; i < mesh.faces.size(); ++i) { //    GL_POLYGON      (  - 0) osg::ref_ptr<osg::DrawElementsUInt> polygon = new osg::DrawElementsUInt(osg::PrimitiveSet::POLYGON, 0); //       for (size_t j = 0; j < mesh.faces[i].indices.size(); ++j) polygon->push_back(mesh.faces[i].indices[j]); //     geom->addPrimitiveSet(polygon.get()); } //    geom->setNormalArray(mesh.normals.get()); //  OpenGL,       geom->setNormalBinding(osg::Geometry::BIND_PER_PRIMITIVE_SET); //             osg::ref_ptr<osg::Geode> geode = new osg::Geode; geode->addDrawable(geom.get()); //     return geode.release(); } 

Di akhir file main.cpp, panggil makro REGISTER_OSGPLUGIN ().

 REGISTER_OSGPLUGIN( pmd, ReaderWriterPMD ) 

Makro ini menghasilkan kode tambahan yang memungkinkan OSG, dalam bentuk pustaka osgDB, untuk membangun objek tipe ReaderWriterPMD dan memanggil metode-metodenya untuk memuat file tipe PMD. Dengan demikian, kerangka kerja plugin siap, masalahnya tetap kecil - untuk mengimplementasikan pemuatan dan penguraian file pmd.

7. Parsim file model 3D


Sekarang semua fungsi plugin bergantung pada implementasi metode parsePMD ()

 pmd_mesh_t ReaderWriterPMD::parsePMD(std::istream &stream) const { pmd_mesh_t mesh; //    while (!stream.eof()) { //      std::string line; std::getline(stream, line); //     -     std::vector<std::string> tokens = parseLine(line); //    -  if (tokens[0] == "vertex") { //       osg::Vec3 point; std::istringstream iss(tokens[1]); iss >> point.x() >> point.y() >> point.z(); //      mesh.vertices->push_back(point); } //    -  if (tokens[0] == "face") { //         unsigned int idx = 0; std::istringstream iss(tokens[1]); face_t face; while (!iss.eof()) { iss >> idx; face.indices.push_back(idx); } //      mesh.faces.push_back(face); //     mesh.normals->push_back(mesh.calcFaceNormal(face)); } } return mesh; } 

Metode ParseLine () mem-parsing baris file PMD

 std::vector<std::string> ReaderWriterPMD::parseLine(const std::string &line) const { std::vector<std::string> tokens; //   ,        ( Windows) std::string tmp = delete_symbol(line, '\r'); size_t pos = 0; std::string token; //      ,     : //      while ( (pos = tmp.find(':')) != std::string::npos ) { //     (vertex  face   ) token = tmp.substr(0, pos); //         tmp.erase(0, pos + 1); if (!token.empty()) tokens.push_back(token); } //        tokens.push_back(tmp); return tokens; } 

Metode ini akan mengubah string "vertex: 1.0 -1.0 0.0" menjadi daftar dari dua baris "vertex" dan "1.0 -1.0 0.0". Pada baris pertama, kami mengidentifikasi tipe data - titik atau wajah, dari baris kedua kami mengekstrak data pada koordinat titik tersebut. Untuk memastikan operasi metode ini, kita memerlukan fungsi bantu delete_symbol (), yang menghapus karakter yang diberikan dari string dan mengembalikan string yang tidak mengandung karakter ini

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

Artinya, sekarang kami telah menerapkan semua fungsi plugin kami dan dapat mengujinya.

8. Menguji plugin


Kami mengkompilasi plugin dan menjalankan debugging (F5). Versi debug penampil osgviewerd standar akan diluncurkan, yang akan menganalisis file piramide.pmd yang diteruskan ke sana, memuat plugin kami dan memanggil metode readNode (). Jika kita melakukan segalanya dengan benar, maka kita akan mendapatkan hasil seperti itu.



Ternyata daftar simpul dan wajah dalam file 3D model yang kita buat menyembunyikan piramida segi empat.

Mengapa kami menghitung sendiri normals? Dalam salah satu pelajaran, kami ditawari metode perhitungan otomatis normals smoothed berikut

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

Kami menerapkan fungsi ini dalam contoh kami, alih-alih menetapkan normals kami sendiri

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

dan kami mendapatkan hasil sebagai berikut:



Normalnya mempengaruhi perhitungan model pencahayaan, dan kami melihat bahwa dalam situasi ini, normals yang dihaluskan menyebabkan hasil perhitungan pencahayaan piramida yang salah. Karena alasan inilah kami menerapkan motor kami ke perhitungan normal. Tetapi saya berpikir bahwa menjelaskan nuansa ini berada di luar cakupan pelajaran ini.

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


All Articles