OpenSceneGraph: الرسم البياني للمشهد والمؤشرات الذكية

الصورة

مقدمة


في مقال سابق ، نظرنا إلى تجميع OpenSceneGraph من المصدر وكتبنا مثالًا أوليًا حيث يتم تعليق الطائرة الرمادية في عالم أرجواني فارغ. أوافق ، ليست مثيرة للإعجاب. ومع ذلك ، كما قلت سابقًا ، في هذا المثال الصغير ، هناك المفاهيم الرئيسية التي يعتمد عليها محرك الرسومات هذا. دعونا ننظر فيها بمزيد من التفصيل. تستخدم المادة أدناه رسومات توضيحية من مدونة ألكسندر بوبكوف حول OSG (من المؤسف أن المؤلف تخلى عن الكتابة عن OSG ...). تعتمد المقالة أيضًا على المواد والأمثلة من كتاب OpenSceneGraph 3.0. دليل المبتدئين

يجب أن أقول أن المنشور السابق تعرض لبعض الانتقادات التي أوافق عليها جزئياً - لقد خرجت المادة دون أن تقال وأخذت خارج السياق. سأحاول إصلاح هذا الإغفال تحت القطع.

1. باختصار حول الرسم البياني للمشهد وعقده


المفهوم المركزي للمحرك هو ما يسمى الرسم البياني للمشهد (ليس من قبيل المصادفة أنه تم تعليقه باسم الإطار نفسه) - هيكل شجرة هرمي يسمح لك بتنظيم تمثيل منطقي ومكاني لمشهد ثلاثي الأبعاد. يحتوي الرسم البياني للمشهد على العقدة الجذرية والعقد أو العقد المتوسطة أو الطرفية المرتبطة بها.

على سبيل المثال



يصور هذا الرسم البياني مشهدًا يتكون من منزل وجدول فيه. يحتوي المنزل على تمثيل هندسي معين ويقع بطريقة معينة في الفضاء بالنسبة لنظام إحداثيات أساسي معين مرتبط بالعقدة الجذرية (الجذر). يتم وصف الجدول أيضًا بواسطة بعض الأشكال الهندسية ، التي تقع بطريقة ما نسبة إلى المنزل ، وإلى جانب المنزل - بالنسبة إلى العقدة الجذرية. تنقسم جميع العقد ، التي لها خاصية مشتركة ، لأنها ترث من فئة osg :: Node واحدة ، إلى أنواع وفقًا للغرض الوظيفي

  1. عقد المجموعة (osg :: Group) - هي الفئة الأساسية لجميع العقد الوسيطة ومصممة لدمج العقد الأخرى في مجموعات
  2. عقد التحويل (osg :: Transform وأحفادها) - مصممة لوصف تحويل إحداثيات الكائن
  3. العقد الهندسية (osg :: Geode) - العقد الطرفية (ورقة) من الرسم البياني للمشهد الذي يحتوي على معلومات حول كائن هندسي واحد أو أكثر.

يتم وصف هندسة كائنات المشهد في OSG في نظام الإحداثيات المحلي الخاص بها. تقوم عقد التحويل الواقعة بين هذا الكائن والعقدة الجذرية بتنفيذ تحويلات مصفوفة الإحالات للحصول على موضع الكائن في نظام الإحداثيات الأساسي.

تؤدي العقد العديد من الوظائف المهمة ، على وجه الخصوص ، تخزن حالة عرض الكائنات ، وتؤثر هذه الحالة فقط على الرسم الفرعي المرتبط بهذه العقدة. يمكن ربط العديد من عمليات الاسترداد بالعقد في الرسم البياني للمشهد ، ومعالجات الأحداث التي تسمح لك بتغيير حالة العقدة والرسم الفرعي المرتبط بها.

يتم تنفيذ جميع العمليات العالمية على الرسم البياني للمشهد المرتبطة بالحصول على النتيجة النهائية على الشاشة تلقائيًا بواسطة المحرك ، من خلال اجتياز الرسم البياني بعمق بشكل دوري.

في المثال الذي تم فحصه آخر مرة ، تألف المشهد من كائن واحد - نموذج طائرة محمل من ملف. بالنظر إلى المستقبل البعيد ، سأقول أن هذا النموذج هو العقدة الورقية للرسم البياني للمشهد. يتم لحامها بإحكام لنظام إحداثيات القاعدة العالمية للمحرك.

2. إدارة الذاكرة OSG


نظرًا لأن عقد الرسم البياني للمشهد تخزن الكثير من البيانات حول كائنات المشهد والعمليات عليها ، فمن الضروري تخصيص الذاكرة ، بما في ذلك ديناميكيًا ، لتخزين هذه البيانات. في هذه الحالة ، عند معالجة الرسم البياني للمشهد ، وحذف بعض العقد الخاصة به ، على سبيل المثال ، تحتاج إلى مراقبة بعناية أن العقد المحذوفة من الرسم البياني لم تعد معالجة. هذه العملية دائمًا ما تكون مصحوبة بأخطاء ، وتصحيح الأخطاء التي تستغرق وقتًا طويلاً ، نظرًا لأنه من الصعب جدًا على المطور تتبع أي مؤشرات للكائنات تشير إلى البيانات الموجودة وأيها يجب حذفها. بدون إدارة فعالة للذاكرة ، من المرجح أن تحدث أخطاء التجزئة وتسربات الذاكرة.

إدارة الذاكرة هي مهمة حاسمة في OSG ويستند مفهومها على نقطتين:

  1. تخصيص الذاكرة: ضمان تخصيص مقدار الذاكرة اللازمة لتخزين كائن.
  2. تحرير الذاكرة: إعادة الذاكرة المخصصة للنظام عند عدم الحاجة إليها.

تستخدم العديد من لغات البرمجة الحديثة ، مثل C # و Java و Visual Basic .Net وما شابه ، ما يسمى بجمع القمامة لتحرير الذاكرة المخصصة. لا يوفر مفهوم لغة C ++ مثل هذا النهج ، ومع ذلك ، يمكننا تقليده باستخدام ما يسمى المؤشرات الذكية.

اليوم ، يحتوي C ++ على مؤشرات ذكية في ترسانته ، والتي تسمى "خارج الصندوق" (وقد نجح معيار C ++ 17 بالفعل في تخليص اللغة من بعض أنواع المؤشرات الذكية المتقادمة) ، لكن هذا لم يكن دائمًا هو الحال. ولد أقدم إصدارات OSG الرسمية رقم 0.9 في عام 2002 ، وكانت هناك ثلاث سنوات أخرى قبل الإصدار الرسمي الأول. في ذلك الوقت ، لم يوفر معيار C ++ حتى الآن مؤشرات ذكية ، وحتى إذا كنت تعتقد أن أحد الانحدارات التاريخية ، فإن اللغة نفسها كانت تمر بأوقات عصيبة. لذا فإن ظهور دراجة على شكل مؤشرات ذكية خاصة بها ، والتي يتم تنفيذها في OSG ، ليس مفاجئًا على الإطلاق. تم دمج هذه الآلية بعمق في هيكل المحرك ، لذا فإن فهم تشغيلها ضروري للغاية من البداية.

3. osg :: ref_ptr <> و osg :: فئات مرجعية


توفر OSG آلية المؤشر الذكية الخاصة بها استنادًا إلى فئة القالب osg :: ref_ptr <> لتنفيذ تجميع البيانات المهملة التلقائي. من أجل تشغيلها الصحيح ، توفر OSG فئة osg :: Referenced أخرى لإدارة كتل الذاكرة التي يتم حساب مرجعيتها لها.

توفر الفئة osg :: ref_ptr <> العديد من عوامل التشغيل والأساليب.

  • get () هي طريقة عامة تُرجع مؤشرًا خامًا ، على سبيل المثال ، عند استخدام قالب osg :: Node كوسيطة ، ستُرجع هذه الطريقة osg :: Node *.
  • العامل * () هو في الواقع عامل الإشارة.
  • عامل التشغيل -> () والعامل = () - يسمح لك باستخدام osg :: ref_ptr <> كمؤشر كلاسيكي عند الوصول إلى طرق وخصائص الكائنات التي يصفها هذا المؤشر.
  • عامل التشغيل == () ، عامل التشغيل! = () وعامل! () - يسمح لك بإجراء عمليات مقارنة على المؤشرات الذكية.
  • valid () هي طريقة عامة تُرجع true إذا كان للمؤشر المُدار القيمة الصحيحة (وليس NULL). التعبير some_ptr.valid () يكافئ التعبير some_ptr! = NULL إذا كان some_ptr مؤشرًا ذكيًا.
  • release () هي طريقة عامة مفيدة عندما تريد إرجاع عنوان مُدار من دالة. حول سيتم وصفه بمزيد من التفصيل في وقت لاحق.

فئة osg :: Referenced هي الفئة الأساسية لجميع عناصر الرسم البياني للمشهد ، مثل العقد والهندسة وحالات التقديم والكائنات الأخرى الموضوعة على الجزء الرئيسي. وبالتالي ، عند إنشاء العقدة الجذرية للمشهد ، نرث بشكل غير مباشر جميع الوظائف التي توفرها فئة osg :: Referenced. لذلك ، في برنامجنا هناك إعلان

osg::ref_ptr<osg::Node> root; 

تحتوي الفئة osg :: Referenced على عداد صحيح للإشارات إلى كتلة الذاكرة المخصصة. تتم تهيئة هذا العداد إلى الصفر في مُنشئ الفئة. زيادة بمقدار واحد عند إنشاء كائن osg :: ref_ptr <>. ينخفض ​​هذا العداد بمجرد حذف أي إشارة إلى الكائن الموصوف بواسطة هذا المؤشر. يتم تدمير الكائن تلقائيًا عندما تتوقف أي مؤشرات ذكية عن الإشارة إليه.

Osg :: الفئة المرجعية لها ثلاث طرق عامة:

  • المرجع () هو أسلوب عام يزيد بمقدار مرجع واحد.
  • unref () هي طريقة عامة ، تتناقص بمقدار مرجع واحد.
  • ReferenceCount () هي طريقة عامة تُرجع القيمة الحالية للعداد المرجعي ، وهي مفيدة عند تصحيح الأخطاء البرمجية.

تتوفر هذه الطرق في جميع الفئات المشتقة من osg :: Referenced. ومع ذلك ، يجب أن نتذكر أن التحكم اليدوي في عداد الارتباط يمكن أن يؤدي إلى عواقب لا يمكن التنبؤ بها ، واستخدام هذا يجب أن تفهم بوضوح ما تقوم به.

4. كيف تجمع OSG القمامة ولماذا هي ضرورية


هناك عدة أسباب لاستخدام المؤشرات الذكية وجمع القمامة:

  • تقليل الأخطاء الفادحة: يسمح لك استخدام المؤشرات الذكية بأتمتة تخصيص الذاكرة وتحريرها. لا توجد مؤشرات خام خطيرة.
  • إدارة فعالة للذاكرة: يتم تحرير الذاكرة المخصصة للكائن على الفور ، بمجرد أن يصبح الكائن غير ضروري ، مما يؤدي إلى الاستخدام الاقتصادي لموارد النظام.
  • تسهيل تصحيح التطبيق: لدينا القدرة على تتبع عدد الروابط إلى كائن بشكل واضح ، ولدينا فرص لأنواع مختلفة من التحسينات والتجارب.

افترض أن الرسم البياني للمشهد يتكون من عقدة جذر وعدة مستويات من العقد الفرعية. إذا تمت إدارة العقدة الجذرية وجميع العقد الفرعية باستخدام فئة osg :: ref_ptr <> ، فبإمكان التطبيق تتبع المؤشر فقط إلى العقدة الجذرية. ستؤدي إزالة هذه العقدة إلى إزالة تلقائية لجميع العقد الفرعية.



يمكن استخدام المؤشرات الذكية كمتغيرات محلية ومتغيرات عامة وأعضاء الفئة وتقليل العدد المرجعي تلقائيًا عند خروج المؤشر الذكي عن النطاق.

ينصح مطورو OSG بشدة باستخدام المؤشرات الذكية لاستخدامها في المشاريع ، ولكن هناك بعض النقاط الأساسية التي يجب الانتباه إليها:

  • مثيلات osg :: المشار إليها ومشتقاتها يمكن إنشاؤها حصريًا على كومة الذاكرة المؤقتة. لا يمكن إنشاؤها على المكدس كمتغيرات محلية ، حيث يتم الإعلان عن المدمرات من هذه الفئات على أنها محمية. على سبيل المثال

 osg::ref_ptr<osg::Node> node = new osg::Node; //  osg::Node node; //  

  • يمكنك إنشاء عقد مشهد مؤقتة باستخدام مؤشرات C ++ العادية ، ومع ذلك ، فإن هذا النهج سيكون غير آمن. من الأفضل استخدام المؤشرات الذكية لضمان إدارة الرسم البياني للمشهد بشكل صحيح.

 osg::Node *tmpNode = new osg::Node; //  ,  ... osg::ref_ptr<osg::Node> node = tmpNode; //         ! 

  • لا يجب أن تستخدم مشاهد الارتباط الدوري في الشجرة بأي حال من الأحوال عندما تشير العقدة إلى نفسها بشكل مباشر أو غير مباشر من خلال عدة مستويات



في المثال البياني للرسم البياني للمشهد ، تشير العقدة التابعة 1.1 إلى نفسها ، وتشير العقدة الفرعية 2.2 أيضًا إلى العقدة الفرعية 1.2. يمكن أن يؤدي هذا النوع من الروابط إلى حساب غير صحيح لعدد الروابط والسلوك غير المحدد للبرنامج.

5. تتبع الكائنات المدارة


لتوضيح تشغيل آلية المؤشر الذكي في OSG ، نكتب المثال الاصطناعي التالي

main.h

 #ifndef MAIN_H #define MAIN_H #include <osg/ref_ptr> #include <osg/Referenced> #include <iostream> #endif // MAIN_H 

main.cpp

 #include "main.h" class MonitoringTarget : public osg::Referenced { public: MonitoringTarget(int id) : _id(id) { std::cout << "Constructing target " << _id << std::endl; } protected: virtual ~MonitoringTarget() { std::cout << "Dsetroying target " << _id << std::endl; } int _id; }; int main(int argc, char *argv[]) { (void) argc; (void) argv; osg::ref_ptr<MonitoringTarget> target = new MonitoringTarget(0); std::cout << "Referenced count before referring: " << target->referenceCount() << std::endl; osg::ref_ptr<MonitoringTarget> anotherTarget = target; std::cout << "Referenced count after referring: " << target->referenceCount() << std::endl; return 0; } 

نقوم بإنشاء فئة osg :: Referenced سلالة لا تفعل شيئًا إلا في المُنشئ والمُدمِّر أنه يُبلغ عن إنشاء مثيله ويعرض المعرف الذي يتم تحديده عند إنشاء المثيل. إنشاء مثيل للفئة باستخدام آلية المؤشر الذكي

 osg::ref_ptr<MonitoringTarget> target = new MonitoringTarget(0); 

بعد ذلك ، نعرض العداد المرجعي للكائن الهدف

 std::cout << "Referenced count before referring: " << target->referenceCount() << std::endl; 

بعد ذلك ، قم بإنشاء مؤشر ذكي جديد ، بتعيينه قيمة المؤشر السابق

 osg::ref_ptr<MonitoringTarget> anotherTarget = target; 

وعرض العداد المرجعي مرة أخرى

 std::cout << "Referenced count after referring: " << target->referenceCount() << std::endl; 

دعونا نرى ما حصلنا عليه من خلال تحليل مخرجات البرنامج

 15:42:39:   Constructing target 0 Referenced count before referring: 1 Referenced count after referring: 2 Dsetroying target 0 15:42:42:   

عندما يبدأ مُنشئ الصف ، يتم عرض رسالة مقابلة تخبرنا أن ذاكرة الكائن مخصصة وأن المُنشئ يعمل بشكل جيد. علاوة على ذلك ، بعد إنشاء مؤشر ذكي ، نرى أن العداد المرجعي للكائن الذي تم إنشاؤه قد زاد بمقدار واحد. إن إنشاء مؤشر جديد ، وتعيينه قيمة المؤشر القديم يؤدي بشكل أساسي إلى إنشاء ارتباط جديد لنفس الكائن ، وبالتالي فإن العداد المرجعي يزداد بآخر. عند خروج البرنامج ، يتم استدعاء المدمر لفئة MonitoringTarget.



دعونا نجري تجربة أخرى عن طريق إضافة مثل هذا الرمز إلى نهاية الوظيفة الرئيسية ()

 for (int i = 1; i < 5; i++) { osg::ref_ptr<MonitoringTarget> subTarget = new MonitoringTarget(i); } 

مما يؤدي إلى مثل هذا البرنامج "العادم"

 16:04:30:   Constructing target 0 Referenced count before referring: 1 Referenced count after referring: 2 Constructing target 1 Dsetroying target 1 Constructing target 2 Dsetroying target 2 Constructing target 3 Dsetroying target 3 Constructing target 4 Dsetroying target 4 Dsetroying target 0 16:04:32:   

نقوم بإنشاء العديد من الكائنات في جسم الحلقة ، باستخدام مؤشر ذكي. نظرًا لأن نطاق المؤشر يمتد في هذه الحالة فقط إلى نص الحلقة ، عند خروجه ، يتم استدعاء المدمر تلقائيًا. لن يحدث هذا ، من الواضح تمامًا ، سنستخدم المؤشرات المعتادة.

يعد تحرير الذاكرة تلقائيًا ميزة مهمة أخرى للعمل مع المؤشرات الذكية. نظرًا لأن مُدمِّر فئة المشتق osg :: Referenced محمي ، لا يمكننا استدعاء عامل الحذف صراحة لحذف الكائن. الطريقة الوحيدة لحذف كائن هي إعادة تعيين عدد الروابط إليه. ولكن بعد ذلك تصبح الشفرة الخاصة بنا غير آمنة أثناء معالجة البيانات متعددة الخيوط - يمكننا الوصول إلى كائن محذوف بالفعل من مؤشر ترابط آخر.

لحسن الحظ ، توفر OSG حلاً لهذه المشكلة بمساعدة برنامج جدولة إزالة الكائن الخاص بها. يعتمد هذا المجدول على استخدام فئة osg :: DeleteHandler. يعمل بطريقة لا تؤدي عملية حذف كائن على الفور ، ولكنها تؤديه بعد فترة. يتم تخزين جميع الكائنات المراد حذفها مؤقتًا حتى تأتي اللحظة للحذف الآمن ، ثم يتم حذفها جميعًا مرة واحدة. يتم التحكم في جدولة إزالة osg :: DeleteHandler بواسطة الواجهة الخلفية للعرض OSG.

6. العودة من الوظيفة


أضف الوظيفة التالية إلى رمز المثال الخاص بنا

 MonitoringTarget *createMonitoringTarget(int id) { osg::ref_ptr<MonitoringTarget> target = new MonitoringTarget(id); return target.release(); } 

واستبدال المكالمة إلى عامل التشغيل الجديد في الحلقة بالاتصال بهذه الوظيفة

 for (int i = 1; i < 5; i++) { osg::ref_ptr<MonitoringTarget> subTarget = createMonitoringTarget(i); } 

ستقلل الاستدعاء () عدد المراجع إلى الكائن إلى صفر ، ولكن بدلاً من حذف الذاكرة ، فإنه يعيد المؤشر الفعلي إلى الذاكرة المخصصة مباشرة. إذا تم تعيين هذا المؤشر إلى مؤشر ذكي آخر ، فلن يكون هناك تسرب للذاكرة.

الاستنتاجات


تعد مفاهيم الرسم البياني للمشهد والمؤشرات الذكية أساسية لفهم مبدأ التشغيل ، وبالتالي الاستخدام الفعال لـ OpenSceneGraph. فيما يتعلق بالمؤشرات الذكية OSG ، تذكر أن استخدامها ضروري للغاية عندما

  • تخزين طويل الأمد للمنشأة
  • يقوم كائن واحد بتخزين ارتباط إلى كائن آخر
  • يجب عليك إرجاع مؤشر من دالة

رمز عينة المقدمة في المقالة متاح هنا .

يتبع ...

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


All Articles