
مقدمة
في مكان ما في الدروس السابقة ، قيل بالفعل أن OSG يدعم تحميل أنواع مختلفة من الموارد مثل الصور النقطية ، والنماذج ثلاثية الأبعاد ذات التنسيقات المختلفة ، أو على سبيل المثال الخطوط من خلال نظام المكونات الخاص بها. المكون الإضافي OSG هو مكون منفصل يعمل على توسيع وظائف المحرك وله واجهة موحدة داخل OSG. يتم تطبيق البرنامج المساعد كمكتبة مشتركة ديناميكية (dll على Windows ، وهكذا على Linux ، إلخ). تتوافق أسماء مكتبات المكونات الإضافية مع اتفاقية محددة
osgdb_< >.dll
أي أن اسم البرنامج المساعد يحتوي دائمًا على بادئة osgdb_. يخبر ملحق الملف المحرك الذي يجب استخدام المكون الإضافي لتنزيل ملف بهذا الملحق. على سبيل المثال ، عندما نكتب وظيفة في التعليمات البرمجية
osg::ref_ptr<osg::Node> model = osgDB::readNodeFile("cessna.osg");
يرى المحرك امتداد osg ويقوم بتحميل مكون إضافي باسم osgdb_osg.dll (أو osgdb_osg.so في حالة Linux). يعمل رمز البرنامج المساعد في كل الأعمال القذرة من خلال إرجاع مؤشر إلى عقدة تصف نموذج سيسنا. بالمثل ، محاولة تحميل صورة PNG
osg::ref_ptr<osg:Image> image = osgDB::readImageFile("picture.png");
سيؤدي إلى تحميل المكون الإضافي osgdb_png.dll ، والذي يقوم بتنفيذ خوارزمية لقراءة البيانات من صورة PNG ووضع هذه البيانات في كائن من النوع osg :: Image.
يتم تنفيذ جميع عمليات العمل باستخدام الموارد الخارجية من خلال وظائف مكتبة osgDB ، التي نربط بها البرامج دائمًا من مثال إلى مثال. تعتمد هذه المكتبة على نظام المكونات OSG. حتى الآن ، تتضمن حزمة OSG العديد من المكونات الإضافية التي تعمل مع معظم تنسيقات الصور والنماذج ثلاثية الأبعاد والخطوط المستخدمة في الممارسة. توفر الإضافات كلاً من بيانات القراءة (الاستيراد) بتنسيق معين ، وفي معظم الحالات ، كتابة البيانات إلى ملف بالتنسيق المطلوب (تصدير). تتيح لك الأداة المساعدة osgconv ، على وجه الخصوص ، تحويل البيانات من تنسيق إلى آخر ، على سبيل المثال ، نظام المكونات الإضافية.
$ osgconv cessna.osg cessna.3ds
إنه يحول بسهولة وبشكل طبيعي نموذج cessna osg إلى تنسيق 3DS ، والذي يمكن استيراده بعد ذلك إلى محرر ثلاثي الأبعاد ، على سبيل المثال ، إلى Blender (بالمناسبة ، هناك
امتداد للعمل مع osg مباشرة لـ Blender)

هناك قائمة رسمية بمكونات OSG القياسية تحتوي على وصف للغرض منها ، لكنها طويلة وأنا كسول جدًا لأحضرها هنا. من الأسهل إلقاء نظرة على مسار تثبيت المكتبة في مجلد bin / ospPlugins-xyz ، حيث x ، y ، z هو رقم إصدار OSG. من اسم ملف البرنامج المساعد ، من السهل فهم التنسيق الذي يعالجه.
إذا تم تجميع OSG بواسطة برنامج التحويل البرمجي MinGW ، تتم إضافة بادئة إضافية mingw_ إلى الاسم القياسي للمكون الإضافي ، أي سيبدو الاسم كما يلي
mingw_osgdb_< >.dll
تم تجهيز إصدار المكون الإضافي الذي تم تجميعه في تكوين DEBUG بالإضافة إلى ذلك باللاحقة d في نهاية الاسم ، أي أن التنسيق سيكون
osgdb_< >d.dll
او
mingw_osgdb_< >d.dll
عند تجميع MinGW.
1. الإضافات الزاحف لوادر
تؤدي بعض إضافات OSG وظائف ما يسمى بالوادر الزائفة - وهذا يعني أنها غير مرتبطة بملحق ملف محدد ، ولكن بإضافة لاحقة إلى نهاية اسم الملف ، يمكنك تحديد المكون الإضافي الذي يجب استخدامه لتنزيل هذا الملف ، على سبيل المثال
$ osgviewer worldmap.shp.ogr
في هذه الحالة ، يكون الاسم الحقيقي للملف الموجود على القرص هو worldmap.shp - يخزن هذا الملف خريطة العالم بتنسيق ESRI shapefile. يخبر اللاحقة .ogr مكتبة osgDB باستخدام المكون الإضافي osgdb_ogr لتحميل هذا الملف ؛ خلاف ذلك ، سيتم استخدام البرنامج المساعد osgdb_shp.
مثال جيد آخر هو البرنامج المساعد osgdb_ffmpeg. تدعم مكتبة FFmpeg أكثر من 100 برنامج ترميز مختلف. لقراءة أي منها ، يمكننا ببساطة إضافة اللاحقة .ffmpeg بعد اسم ملف الوسائط.
بالإضافة إلى ذلك ، تسمح لنا بعض اللوادر المزيفة بالمرور عبر لاحقة عدد من المعلمات التي تؤثر على حالة الكائن الذي تم تحميله ، وقد صادفنا هذا بالفعل في مثال مع الرسوم المتحركة
node = osgDB::readNodeFile("cessna.osg.0,0,90.rot");
يشير السطر 0.90 إلى المكون الإضافي osgdb_osg إلى معلمات الاتجاه الأولي للنموذج الذي تم تحميله. تتطلب بعض اللوادر الزائفة معلمات محددة تمامًا للعمل.
2. API لتطوير الإضافات الخارجية
من المنطقي تمامًا إذا كان لديك ، بعد كل القراءة ، فكرة أنه قد لا يكون من الصعب كتابة المكوّن الإضافي الخاص بك لـ OSG ، مما يسمح لك باستيراد تنسيق غير قياسي للنماذج أو الصور ثلاثية الأبعاد. وهذا هو الفكر الحقيقي! تم تصميم آلية البرنامج المساعد لتوسيع وظائف المحرك دون تغيير OSG نفسه. لفهم المبادئ الأساسية لكتابة البرنامج المساعد ، دعونا نحاول تنفيذ مثال بسيط.
تطوير المكوّن الإضافي هو توسيع واجهة القراءة / الكتابة الافتراضية التي توفرها OSG. يتم توفير هذه الوظيفة بواسطة الفئة الظاهري osgDB :: ReaderWriter. توفر هذه الفئة عددًا من الطرق الافتراضية التي تم إعادة تعريفها بواسطة مطور البرنامج المساعد.
الطريقة | الوصف |
---|
يدعمالتوسعات () | يقبل معلمتين من السلسلة: امتداد الملف والوصف. يتم استدعاء الطريقة دائمًا في مُنشئ الفئة الفرعية. |
يقبل extension () | يعود صحيحًا إذا تم دعم الملحق الذي تم تمريره كوسيطة بواسطة المكون الإضافي |
fileExists () | يتيح لك تحديد ما إذا كان هناك ملف معين موجود (يتم تمرير المسار كمعلمة) على القرص (يتم إرجاع true إذا نجح) |
readNode () | يقبل اسم الملف وخياراته ككائن osgDB :: Option. يتم تنفيذ وظائف قراءة البيانات من ملف من قبل المطور |
writeNode () | يقبل اسم العقدة واسم الملف المطلوب والخيارات. يتم تنفيذ وظائف كتابة البيانات على القرص بواسطة المطور |
readImage () | قراءة بيانات الصورة النقطية |
writeImage () | كتابة صورة نقطية على القرص |
يمكن وصف تطبيق طريقة readNode () بالشفرة التالية
osgDB::ReaderWriter::ReadResult readNode( const std::string &file, const osgDB::Options *options) const {
من المستغرب قليلاً أنه بدلاً من مؤشر إلى عقدة الرسم البياني للمشهد ، تقوم الطريقة بإرجاع النوع osgDB :: ReaderWriter :: ReadResult. هذا النوع هو كائن نتيجة للقراءة ، ويمكن استخدامه كحاوية للعقدة ، أو صورة ، أو عداد لحالة (على سبيل المثال ، FILE_NOT_FOUND) ، أو كائن خاص آخر ، أو حتى كسلسلة رسائل خطأ. لديها العديد من المنشئات الضمنية لتنفيذ الوظائف الموصوفة.
فئة مفيدة أخرى هي osgDB :: Options. يمكن أن يسمح لك بتعيين أو الحصول على سلسلة من خيارات التحميل باستخدام أساليب setOptionString () و getOptionString (). يُسمح أيضًا بتمرير هذه السلسلة إلى مُنشئ هذه الفئة كوسيطة.
يمكن للمطور التحكم في سلوك المكون الإضافي عن طريق تعيين الإعدادات في سلسلة المعلمة التي تم تمريرها عند تحميل الكائن ، على سبيل المثال ، بهذه الطريقة
3. معالجة تدفق البيانات في البرنامج المساعد OSG
تتضمن الفئة الأساسية osgDB :: ReaderWriter مجموعة من الطرق التي تعالج بيانات تدفقات الإدخال / الإخراج التي توفرها مكتبة C ++ القياسية. الاختلاف الوحيد بين أساليب القراءة / الكتابة وطرق المناقشة أعلاه هو أنه بدلاً من اسم الملف ، يقبلون std :: istream & تدفقات الإدخال أو std :: ostream & output. يفضل استخدام دفق إدخال / إخراج الملف دائمًا استخدام اسم الملف. لتنفيذ عمليات قراءة الملفات ، يمكننا استخدام تصميم الواجهة التالي:
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 {
بعد تطبيق المكوّن الإضافي ، يمكننا استخدام الدالتين القياسيتين osgDB :: readNodeFile () و osgDB :: readImageFile () لتحميل النماذج والصور ، ببساطة عن طريق تحديد مسار الملف. سوف تجد OSG وتنزيل المكون الإضافي الذي كتبناه.
4. نكتب المساعد الخاص بنا
لذلك ، لا أحد يزعجنا أن نأتي بتنسيق خاص بنا لتخزين البيانات على هندسة ثلاثية الأبعاد ، وسوف نأتي بها
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
هنا في بداية الملف قائمة من القمم مع إحداثياتها. ترتيب مؤشرات Vertex بالترتيب ، بدءًا من الصفر. بعد قائمة القمم تأتي قائمة الوجوه. يتم تعريف كل وجه من خلال قائمة مؤشرات قمة الرأس التي يتم تشكيلها منها. على ما يبدو لا شيء معقد. تتمثل المهمة في قراءة هذا الملف من القرص وتشكيل هندسة ثلاثية الأبعاد على أساسه.
5. إعداد مشروع البرنامج المساعد: بناء ميزات البرنامج النصي
إذا كان علينا قبل إنشاء التطبيقات ، فعلينا الآن أن نكتب مكتبة ديناميكية ، وليس مكتبة فحسب ، بل مكون إضافي OSG يلبي متطلبات معينة. سنبدأ في تلبية هذه المتطلبات من خلال برنامج نصي لبناء المشروع سيبدو هكذا
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)
سنقوم بتحليل الفروق الدقيقة بمزيد من التفصيل
TEMPLATE = lib
يعني أننا سنبني المكتبة. لمنع إنشاء روابط رمزية بمساعدة حل مشكلات تعارض إصدار المكتبة في أنظمة * nix ، فإننا نوضح لنظام الإنشاء أن هذه المكتبة ستكون مكونًا إضافيًا ، أي ، سيتم تحميلها في الذاكرة "أثناء التنقل"
CONFIG += plugin
بعد ذلك ، نستبعد إنشاء بادئة lib ، التي تتم إضافتها عند استخدام برنامج التحويل البرمجي للعائلة gcc وتؤخذ في الاعتبار بواسطة بيئة وقت التشغيل عند تحميل المكتبة
CONFIG += no_plugin_name_prefix
اضبط اسم ملف المكتبة
TARGET = osgdb_pmd
حيث pmd هو امتداد ملف تنسيق النموذج الثلاثي الأبعاد الذي اخترعناه. علاوة على ذلك ، يجب أن نشير إلى أنه في حالة تجميع MinGW ، تتم إضافة البادئة mingw_ بالضرورة إلى الاسم
win32-g++: TARGET = $$join(TARGET,,mingw_,)
حدد مسار بناء المكتبة: لنظام Windows
DESTDIR = $$(OSG_PLUGINS_PATH)
لينكس
DESTDIR = /usr/lib/osgPlugins-3.7.0
لنظام التشغيل Linux ، مع مثل هذا المؤشر على المسار (والذي لا شك فيه عكازاً ، لكنني لم أجد حلاً آخر بعد) ، فإننا نعطي الحق في الكتابة إلى المجلد المحدد باستخدام إضافات OSG من مستخدم عادي
# chmod 666 /usr/lib/osgPlugins-3.7.0
تتشابه كافة إعدادات الإنشاء الأخرى مع تلك المستخدمة في تجميع تطبيقات النماذج مسبقًا.
6. إعداد مشروع البرنامج المساعد: ميزات وضع التصحيح
نظرًا لأن هذا المشروع مكتبة ديناميكية ، يجب أن يكون هناك برنامج يقوم بتحميل هذه المكتبة في عملية تنفيذها. يمكن أن يكون أي تطبيق يستخدم OSG والذي سيتم استدعاء الوظيفة فيه
node = osdDB::readNodeFile("piramide.pmd");
في هذه الحالة ، سيتم تحميل البرنامج المساعد لدينا. من أجل عدم كتابة مثل هذا البرنامج بنفسك ، سوف نستخدم حلاً جاهزًا - عارض osgviewer القياسي ، والذي يتم تضمينه في حزمة تسليم المحرك. إذا في وحدة التحكم تنفيذ
$ osgviewer piramide.pmd
بعد ذلك سوف يؤدي أيضا البرنامج المساعد. في إعدادات تشغيل المشروع ، حدد المسار إلى osgviewerd ، كدليل العمل ، وحدد الدليل الذي يوجد به ملف piramide.pmd ، وحدد نفس الملف في خيارات سطر أوامر osgviewer

الآن يمكننا تشغيل البرنامج المساعد وتصحيحه مباشرة من QtCreator IDE.
6. ننفذ إطار البرنامج المساعد
يعمم هذا المثال إلى حد ما المعرفة التي تلقيناها بالفعل حول OSG من الدروس السابقة. عند كتابة البرنامج المساعد ، علينا أن
- حدد بنية بيانات لتخزين معلومات هندسة النماذج التي يتم قراءتها من ملف طراز
- قراءة وتحليل (تحليل) ملف البيانات النموذجي
- قم بتكوين الكائن الهندسي osg :: Drawable بشكل صحيح استنادًا إلى البيانات المقروءة من الملف
- إنشاء مخطط فرعي للمشهد لطراز تم تحميله
لذلك ، حسب التقاليد ، سأقدم شفرة المصدر الكاملة للمكون الإضافي
البرنامج المساعد Osgdb_pmdالرئيسية #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"
أولاً ، دعونا نعتني بالهياكل الخاصة بتخزين البيانات الهندسية.
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()); } };
يتكون الهيكل من متغيرات الأعضاء لتخزين البيانات: القمم - لتخزين مجموعة من القمم لكائن هندسي ؛ normals - مجموعة من الحالات الطبيعية إلى وجوه الكائن ؛ الوجوه - قائمة وجوه وجوه. يقوم مُنشئ الهيكل بتهيئة المؤشرات الذكية على الفور
pmd_mesh_t() : vertices(new osg::Vec3Array) , normals(new osg::Vec3Array) { }
بالإضافة إلى ذلك ، تحتوي البنية على طريقة تسمح لك بحساب المتجه العادي للوجه calcFaceNormal () كمعلمة تأخذ بنية تصف الوجه. لن ندخل في تفاصيل تنفيذ هذه الطريقة بعد ، وسنحللها لاحقًا.
وبالتالي ، قررنا الهياكل التي سنقوم بتخزين بيانات الهندسة فيها. الآن دعنا نكتب إطار المكون الإضافي الخاص بنا ، أي أننا نطبق فئة الوراثة 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; };
على النحو الموصى به في وصف واجهة برمجة التطبيقات لتطوير المكونات الإضافية ، في هذا الفصل ، نعيد تعريف طرق قراءة البيانات من ملف وتحويلها إلى رسم تخطيطي للمشهد. تقوم طريقة readNode () بإجراء حملين زائدين - أحدهما يقبل اسم الملف كمدخل ، والآخر يتلقى دفق إدخال قياسي. يحدد مُنشئ الفصل امتدادات الملفات التي يدعمها البرنامج المساعد
ReaderWriterPMD::ReaderWriterPMD() { supportsExtension("pmd", "PMD model file"); }
يحلل التحميل الزائد الأول من أسلوب readNode () صحة اسم الملف والمسار إليه ، ويربط دفق إدخال قياسي بالملف ، ويستدعي التحميل الزائد الثاني ، الذي يقوم بالعمل الرئيسي
osgDB::ReaderWriter::ReadResult ReaderWriterPMD::readNode( const std::string &filename, const osgDB::Options *options) const {
في التحميل الزائد الثاني ، نقوم بتنفيذ خوارزمية إنشاء الكائن لـ OSG
osgDB::ReaderWriter::ReadResult ReaderWriterPMD::readNode( std::istream &stream, const osgDB::Options *options) const { (void) options;
في نهاية الملف main.cpp ، استدعاء الماكرو REGISTER_OSGPLUGIN ().
REGISTER_OSGPLUGIN( pmd, ReaderWriterPMD )
ينشئ هذا الماكرو رمزًا إضافيًا يسمح لـ OSG ، في شكل مكتبة osgDB ، ببناء كائن من النوع ReaderWriterPMD واستدعاء طرقه لتحميل ملفات من النوع pmd. وبالتالي ، فإن إطار البرنامج المساعد جاهز ، ويبقى الأمر صغيرًا - لتنفيذ التحميل والتحليل في ملف pmd.
7. Parsim 3D ملف النموذج
الآن تعتمد جميع وظائف البرنامج المساعد على تنفيذ طريقة parsePMD ()
pmd_mesh_t ReaderWriterPMD::parsePMD(std::istream &stream) const { pmd_mesh_t mesh;
parseLine() pmd-
std::vector<std::string> ReaderWriterPMD::parseLine(const std::string &line) const { std::vector<std::string> tokens;
"vertex: 1.0 -1.0 0.0" "vertex" " 1.0 -1.0 0.0". — , . delete_symbol(),
std::string delete_symbol(const std::string &str, char symbol) { std::string tmp = str; tmp.erase(std::remove(tmp.begin(), tmp.end(), symbol), tmp.end()); return tmp; }
.
8.
(F5). osgviewerd, piramide.pmd, readNode(). ,

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

, . . , .