
مقدمة
من ناحية ، لدى محرك OpenSceneGraph نفسه نظام فرعي متقدم لإدارة النوافذ ، ومعالجة أحداث إدخال المستخدم ، وإرسال واستقبال رسائل المستخدم. تحدثنا عن هذا بشيء من التفصيل في المقالات السابقة من هذه السلسلة. بشكل عام ، إلى جانب إمكانيات C ++ / STL ، يعد هذا كافيًا تمامًا لتطوير تطبيقات معقدة بشكل تعسفي.
مثال على دمج OSG في تطبيق تم تطويره بواسطة QtDesigner. سيتم مناقشة هذا المثال بالتفصيل أدناه.من ناحية أخرى ، لتسريع عملية التطوير في C ++ ، يتم استخدام كل من مكتبات الطرف الثالث التي تعمل على توسيع قدرات هذه اللغة (مثل دفعة) ، وتتيح لك أطر العمل الكاملة تطوير تطبيقات متعددة المنصات بسهولة وطبيعية للأغراض الوظيفية الواسعة. واحد مثل هذا الإطار هو كيو تي شعبية جدا. بغض النظر عن كيفية تأنيبهم لـ Qt بسبب برنامج التحويل البرمجي للكائنات التعريفية وأوجه القصور والمضايقات الأخرى ، فإن قوة Qt موجودة في مكتبة صفية واسعة النطاق تحل جميع المهام التي يمكن تصورها لتطوير النظام الأساسي المشترك ، وكذلك في مفهوم "الإشارات - فتحات" التي تنفذ نظام المراسلة الفرعي بين الفئات. تعتمد الإشارات والفتحات أيضًا على طرق التفاعل بين التطبيق ونظام التشغيل ، بالإضافة إلى التواصل بين العمليات.
والجحيم ، سيكون من المثير للاهتمام للغاية الجمع بين تقنيتين: Qt و OSG. كان على فريقي حل مشكلة مماثلة ، والتي كتبت عنها بالفعل في
أحد منشوراتي . ومع ذلك ، أود فتح هذا السؤال على نطاق أوسع قليلاً ، وسوف يكون هذا المقال حول هذا الموضوع.
هناك خياران لدمج OSG و Qt:
- استخدام إشارات وفتحات Qt للتفاعل مع الكائنات داخل تطبيق OSG
- دمج عارض OSG في واجهة رسومية تم تطويرها في C ++ / Qt ، بما في ذلك استخدام مصمم نماذج QtDesigner
يكون الخيار الأول قابلاً للتطبيق عندما لا تحتاج إلى استخدام عناصر واجهة المستخدم الرسومية التي يوفرها كيو تي ، ولكنك ترغب في ضمان تفاعل مكونات التطبيق من خلال الإشارات والفتحات. على سبيل المثال ، نشأت مثل هذه الحاجة لدمج تطبيق OSG مع مكتبة اتصال interprocess عبر مآخذ TCP باستخدام Qt.
هناك حاجة إلى الخيار الثاني عندما تكون هناك حاجة إلى تكامل محرك OSG وتطبيق رسومي تم تطويره باستخدام Qt. تصبح الإشارات والفتحات متاحة لنا ، بالإضافة إلى المجموعة الكاملة من عناصر واجهة المستخدم الرسومية الموحدة التي توفرها شركة Qt.
1. إشارات Qt في نظام نافذة OSG
المثال الأول سيكون اصطناعيًا إلى حد ما: نكتب تطبيق OSG بسيط مع مشهد بدائي ؛ قم بإنشاء فئتين ، أحدهما سيتعامل مع ضغطات المفاتيح ، والآخر لعرض رسالة إلى وحدة التحكم حول أي مفتاح يتم الضغط عليه. في هذه الحالة ، سيقوم المعالج بإنشاء إشارة Qt ، مع رسالة حول المفتاح المضغوط كمعلمة.
للتكامل مع Qt ، يكفي تحقيق الشروط الثلاثة التالية
- وراثة الطبقات المتفاعلة من QObject
- تنظيم حلقة معالجة الإشارات
- قم بإنشاء مثيل لفئة QApplication (أو QCoreApplication) الموجودة في الذاكرة أثناء تشغيل التطبيق
يمكن رؤية رمز المثال الكامل
هنا في
مستودع دروس OSG ، والذي يحتوي على جميع الدروس لهذه الدورة.
أولاً ، قم بإنشاء فئة "تطور" قائمة انتظار معالجة الإشارات.
qt-events.h#ifndef QT_EVENTS_H #define QT_EVENTS_H #include <osgGA/GUIEventHandler> #include <QCoreApplication> //------------------------------------------------------------------------------ // //------------------------------------------------------------------------------ class QtEventsHandler : public osgGA::GUIEventHandler { public: QtEventsHandler(); virtual bool handle(const osgGA::GUIEventAdapter &ea, osgGA::GUIActionAdapter &aa); protected: }; #endif // QT_EVENTS_H
qt-events.cpp #include "qt-events.h"
كل شيء بسيط للغاية - هذه الفئة عبارة عن معالج أحداث OSG قياسي ، عند تقديم إطار ، يبدأ معالجة قائمة انتظار إشارة Qt عن طريق الاتصال
QCoreApplication::processEvents(QEventLoop::AllEvents);
لنقم الآن بإنشاء فصل يعالج لوحة المفاتيح ، مرة أخرى ، باستخدام الآلية المدمجة في OSG ، ولكن في نفس الوقت قادر على إرسال إشارة Qt. للقيام بذلك ، سوف نستخدم الوراثة المتعددة التي تم تشريحها مؤخرًا
keyhandler.h #ifndef KEY_HANDLER_H #define KEY_HANDLER_H #include <osgGA/GUIEventHandler> #include <QObject> //------------------------------------------------------------------------------ // //------------------------------------------------------------------------------ class KeyboardHandler : public QObject, public osgGA::GUIEventHandler { Q_OBJECT public: KeyboardHandler(QObject *parent = Q_NULLPTR) : QObject(parent) , osgGA::GUIEventHandler () { } bool handle(const osgGA::GUIEventAdapter &ea, osgGA::GUIActionAdapter &aa) { switch (ea.getEventType()) { case osgGA::GUIEventAdapter::KEYDOWN: emit sendMessage("Pressed key " + QString(ea.getKey())); break; default: break; } return false; } signals: void sendMessage(QString msg); private: }; #endif // KEY_HANDLER_H
سيقوم الفصل بمعالجة رسالة حول مفتاح مضغوط وإرسال إشارة مع رسالة تحتوي على رمز المفتاح المضغوط. سيتم استلام هذه الإشارة بواسطة فئة غير متصلة بأي شكل من الأشكال بـ OSG ، وهو سليل QObject ويحتوي على فتحة واحدة تطبع رسالة إلى الإخراج القياسي
المتلقي #ifndef RECEIVER_H #define RECEIVER_H #include <QObject> #include <iostream> //------------------------------------------------------------------------------ // //------------------------------------------------------------------------------ class Receiver : public QObject { Q_OBJECT public: Receiver(QObject *parent = Q_NULLPTR) : QObject(parent) {} public slots: void printMessage(QString msg) { std::cout << msg.toStdString() << std::endl; } }; #endif // RECEIVER_H
الآن ضع كل ذلك معًا عن طريق كتابة تطبيق OSG
الرئيسية #ifndef MAIN_H #define MAIN_H #include <osgViewer/Viewer> #include <osgDB/ReadFile> #include <QCoreApplication> #include "qt-events.h" #include "keyhandler.h" #include "receiver.h" #endif
main.cpp #include "main.h"
أولاً ، نقوم بإنشاء مثيل لفئة QCoreApplication.
QCoreApplication app(argc, argv);
هذا ضروري للعمل التكنولوجيا الموصوفة. ومع ذلك ،
لن نقوم
باستدعاء الأسلوب QCoreApplication :: exec () ! بدلاً من ذلك ، سوف تدور حلقة معالجة الإشارات داخل حلقة osgViewer :: Viewer :: run () الخاصة بنا ، والتي ننشئ من أجلها ونقوم بتسجيل المعالج المقابل
viewer.addEventHandler(new QtEventsHandler);
نقوم بإنشاء حالات للفئات التي ستتفاعل من خلال إشارات Qt ، وتربط إشارة أحدها بفتحة الآخر
KeyboardHandler *keyboardHandler = new KeyboardHandler; Receiver *receiver = new Receiver; QObject::connect(keyboardHandler, &KeyboardHandler::sendMessage, receiver, &Receiver::printMessage);
تسجيل معالج لوحة المفاتيح
viewer.addEventHandler(keyboardHandler);
جميع تشغيل العارض
return viewer.run();
وانظر مثل هذه الصورة
نعم ، المثال مفتون إلى حد ما ، لكنه يوضح المبادئ الرئيسية لدمج الكود باستخدام آليات Qt في تطبيق يستخدم OSG. هذه الفكرة ، المستقاة من
OpenSceneGraph 3. Cookbook ، وفرت الكثير من الوقت والأعصاب لي وللفريق التطوير الخاص بي ، مما أتاح لي استخدام وحدة قائمة على Qt تم تصحيحها وتوحيدها داخل قاعدة الكود الخاصة بنا في قاعدة الكود الخاصة بنا.
ولكن ماذا لو كنا ما زلنا نريد استخدام OSG داخل تطبيق QT GUI؟
2. مكتبة osgQt
osgQt هي مكتبة تكامل مصممة من أجل:
- تضمين مشهد ثلاثي الأبعاد مطبق على OSG في الواجهة الرسومية لتطبيق تم تطويره على Qt
- تضمين الحاجيات Qt على سطح هندسة ثلاثية الأبعاد داخل مشهد OSG. نعم ، لقد سمعت عن حق - يمكن لعناصر واجهة Qt العمل بهدوء داخل العالم الافتراضي. يوما ما سأثبت ذلك بالتأكيد
كانت هناك بعض المشاكل في هذه المكتبة ، والتي تمكنا من التغلب عليها من خلال دراسة بعناية الأمثلة المرفقة بها وقراءة
كتاب الطبخ OpenSceneGraph 3. الذي سبق ذكره
يجب تجميع المكتبة ، وتشبه هذه العملية تجميع المحرك نفسه ، الموضح بالتفصيل في
أول مقال من الدورة . الملاحظة الوحيدة هي أنه يجب تحديد -DCMAKE_INSTALL_PREFIX بنفس الملاحظة التي تم تحديدها عند بناء المحرك - لذلك سيتم تثبيت osgQt بجوار المحرك ، وسيكون مناسبًا للاستخدام أثناء التطوير.
3. دمج osgViewer :: Viewer في واجهة المستخدم الرسومية كيو تي
المثال التالي سيكون مفيدًا جدًا. سنكتب عارضًا يسمح لك بتحميل نماذج تنسيق .osg باستخدام عناصر تحكم Qt قياسية. علاوة على ذلك ، لتطوير واجهة رسومية ، نستخدم QtDeisgner.
لنقم بإنشاء مشروع جديد مثل "تطبيق Qt Widgets"

في هذه الحالة ، سيتم إنشاء نافذة التطبيق الرئيسية بشريط القوائم وشريط الأدوات وشريط الحالة. في QtDesigner ، أضف مكون QFrame إلى هذه النافذة.

سنضع عارض OSG في هذا الإطار. سيكون عارض OSG عبارة عن عنصر واجهة مستخدم Qt بشكل أساسي ، ولتنفيذه سنكتب فئة QViewerWidget. سأضع المصادر الكاملة على المفسد ، حتى لا يتم طمس العرض التقديمي بأوراق الشفرة
qviewerwidget.h #ifndef QVIEWER_WIDGET_H #define QVIEWER_WIDGET_H #include <QWidget> #include <osgViewer/Viewer> #include <osgQt/GraphicsWindowQt> //------------------------------------------------------------------------------ // //------------------------------------------------------------------------------ class QViewerWidget : public QWidget { public: QViewerWidget(const QRect &geometry); virtual ~QViewerWidget(); osg::Group *getScene(); osgViewer::Viewer *getViewer(); protected: osg::ref_ptr<osg::Group> scene; osgViewer::Viewer viewer; private: osgQt::GraphicsWindowQt *createGraphicsWindow(const QRect &geometry); void initCamera(const QRect &geometry); void paintEvent(QPaintEvent *); }; #endif // QVIEWER_WIDGET_H
qviewerwidget.cpp include "qviewerwidget.h" #include <osgViewer/ViewerEventHandlers> #include <osgGA/TrackballManipulator> #include <QGridLayout> //------------------------------------------------------------------------------ // //------------------------------------------------------------------------------ QViewerWidget::QViewerWidget(const QRect &geometry) : QWidget() , scene(new osg::Group) { initCamera(geometry); viewer.setSceneData(scene); viewer.addEventHandler(new osgViewer::StatsHandler); viewer.setCameraManipulator(new osgGA::TrackballManipulator); viewer.setThreadingModel(osgViewer::Viewer::SingleThreaded); osgQt::GraphicsWindowQt *gw = static_cast<osgQt::GraphicsWindowQt *>(viewer.getCamera()->getGraphicsContext()); QGridLayout *layout = new QGridLayout; if (layout != Q_NULLPTR) { layout->addWidget(gw->getGLWidget()); this->setLayout(layout); } } //------------------------------------------------------------------------------ // //------------------------------------------------------------------------------ QViewerWidget::~QViewerWidget() { } //------------------------------------------------------------------------------ // //------------------------------------------------------------------------------ osg::Group *QViewerWidget::getScene() { return scene.get(); } //------------------------------------------------------------------------------ // //------------------------------------------------------------------------------ osgViewer::Viewer *QViewerWidget::getViewer() { return &viewer; } //------------------------------------------------------------------------------ // //------------------------------------------------------------------------------ osgQt::GraphicsWindowQt *QViewerWidget::createGraphicsWindow(const QRect &geometry) { osg::DisplaySettings *ds = osg::DisplaySettings::instance().get(); osg::ref_ptr<osg::GraphicsContext::Traits> traits = new osg::GraphicsContext::Traits; traits->windowName = ""; traits->windowDecoration = false; traits->x = geometry.x(); traits->y = geometry.y(); traits->width = geometry.width(); traits->height = geometry.height(); if (traits->height == 0) traits->height = 1; traits->doubleBuffer = true; traits->alpha = ds->getMinimumNumAlphaBits(); traits->stencil = ds->getMinimumNumStencilBits(); traits->sampleBuffers = ds->getMultiSamples(); traits->samples = ds->getNumMultiSamples(); return new osgQt::GraphicsWindowQt(traits.get()); } //------------------------------------------------------------------------------ // //------------------------------------------------------------------------------ void QViewerWidget::initCamera(const QRect &geometry) { osg::Camera *camera = viewer.getCamera(); osg::ref_ptr<osgQt::GraphicsWindowQt> gw = createGraphicsWindow(geometry); gw->setTouchEventsEnabled(true); camera->setGraphicsContext(gw.get()); const osg::GraphicsContext::Traits *traits = gw->getTraits(); camera->setClearColor(osg::Vec4(0.7f, 0.7f, 0.7f, 1.0f)); camera->setViewport(0, 0, traits->width, traits->height); double aspect = static_cast<double>(traits->width) / static_cast<double>(traits->height); camera->setProjectionMatrixAsPerspective(30.0, aspect, 1.0, 1000.0); } //------------------------------------------------------------------------------ // //------------------------------------------------------------------------------ void QViewerWidget::paintEvent(QPaintEvent *) { viewer.frame(); }
الفكرة الرئيسية للتطبيق هي استخدام فئة osgQt :: GraphicsWindow ، التي تنشئ نافذة رسومات تستند إلى فئة QGLWidget. لإنشاء هذه النافذة ، استخدم الطريقة
osgQt::GraphicsWindowQt *QViewerWidget::createGraphicsWindow(const QRect &geometry) { osg::DisplaySettings *ds = osg::DisplaySettings::instance().get(); osg::ref_ptr<osg::GraphicsContext::Traits> traits = new osg::GraphicsContext::Traits; traits->windowName = ""; traits->windowDecoration = false; traits->x = geometry.x(); traits->y = geometry.y(); traits->width = geometry.width(); traits->height = geometry.height(); if (traits->height == 0) traits->height = 1; traits->doubleBuffer = true; traits->alpha = ds->getMinimumNumAlphaBits(); traits->stencil = ds->getMinimumNumStencilBits(); traits->sampleBuffers = ds->getMultiSamples(); traits->samples = ds->getNumMultiSamples(); return new osgQt::GraphicsWindowQt(traits.get()); }
تم تكوين النافذة وفقًا للمعلمات الهندسية المرسلة إلى المدخلات والإعدادات المطلوبة لتجسيد OSG 3D. المؤشر المرتجع هو سياق رسومات OSG الذي يجب تمريره إلى الكاميرا. لذلك ، فإن الخطوة التالية هي تهيئة الكاميرا
void QViewerWidget::initCamera(const QRect &geometry) { osg::Camera *camera = viewer.getCamera(); osg::ref_ptr<osgQt::GraphicsWindowQt> gw = createGraphicsWindow(geometry); gw->setTouchEventsEnabled(true); camera->setGraphicsContext(gw.get()); const osg::GraphicsContext::Traits *traits = gw->getTraits(); camera->setClearColor(osg::Vec4(0.7f, 0.7f, 0.7f, 1.0f)); camera->setViewport(0, 0, traits->width, traits->height); double aspect = static_cast<double>(traits->width) / static_cast<double>(traits->height); camera->setProjectionMatrixAsPerspective(30.0, aspect, 1.0, 1000.0); }
المكالمة الفعلية
camera->setGraphicsContext(gw.get());
ويمرر السياق المطلوب المرتبط بـ QGLWidget widget إلى الكاميرا. نضع روتين القطعة بالكامل في مُنشئ الفصل
QViewerWidget::QViewerWidget(const QRect &geometry) : QWidget() , scene(new osg::Group) { initCamera(geometry); viewer.setSceneData(scene); viewer.addEventHandler(new osgViewer::StatsHandler); viewer.setCameraManipulator(new osgGA::TrackballManipulator); viewer.setThreadingModel(osgViewer::Viewer::SingleThreaded); osgQt::GraphicsWindowQt *gw = static_cast<osgQt::GraphicsWindowQt *>(viewer.getCamera()->getGraphicsContext()); QGridLayout *layout = new QGridLayout; if (layout != Q_NULLPTR) { layout->addWidget(gw->getGLWidget()); this->setLayout(layout); } }
هنا نقوم بتهيئة المشاهد وإيلاء اهتمام خاص للتحدي
viewer.setThreadingModel(osgViewer::Viewer::SingleThreaded);
تبديل العارض إلى الوضع المفرد. يعد هذا إجراءً ضروريًا عند دمج OSG في Qt ، لأنه في بعض توزيعات Linux ، سيتعطل البرنامج عند استخدام العرض متعدد الخيوط الذي تستخدمه OSG افتراضيًا. تتطلب أسباب ذلك استجوابًا منفصلاً ، لذلك استمر في ذلك وانتبه إلى هذا الرمز
osgQt::GraphicsWindowQt *gw = static_cast<osgQt::GraphicsWindowQt *>(viewer.getCamera()->getGraphicsContext()); QGridLayout *layout = new QGridLayout; if (layout != Q_NULLPTR) { layout->addWidget(gw->getGLWidget()); this->setLayout(layout); }
الذي ننشئ فيه طبقة ، تتداخل مع QGLWidget التي يتم إرجاعها من سياق رسومات الكاميرا ، والتي يتم تحويلها إلى مؤشر osgQt :: GraphicsWindows. نضيف الطبقة التي تم إنشاؤها إلى القطعة لدينا QViewerWidget عن طريق الاتصال
this->setLayout(layout);
من أجل تطبيقنا المصغر ، ومعه ، يتم تحديث المشهد عند تحديث النافذة ، تحتاج إلى إعادة تعريف معالج أحداث QPaintEvent
void QViewerWidget::paintEvent(QPaintEvent *) { viewer.frame(); }
الذي نبدأ فيه تقديم الإطار عن طريق استدعاء الأسلوب osgViewer :: Viewer :: frame ().
حسنًا ، كود القطعة لدينا جاهز ، والآن قمنا بتضمينه في إطار موجود في النموذج. للقيام بذلك ، في مُنشئ فئة MainWindow نكتب هذا الرمز
MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) , ui(new Ui::MainWindow) , qviewer(Q_NULLPTR) { ui->setupUi(this); QGridLayout *layout = new QGridLayout; qviewer = new QViewerWidget(QRect(0, 0, ui->frame->width(), ui->frame->height())); layout->addWidget(qviewer); ui->frame->setLayout(layout); this->setCentralWidget(ui->frame); connect(&timer, &QTimer::timeout, this, &MainWindow::update); timer.start(40); connect(ui->actionQuit, &QAction::triggered, this, &MainWindow::quit); connect(ui->actionClean, &QAction::triggered, this, &MainWindow::clean); connect(ui->actionOpen, &QAction::triggered, this, &MainWindow::open); this->setWindowTitle("QViewerWidget example"); }
أو بالأحرى ، ما زلنا مهتمين بهذا الجزء منه
QGridLayout *layout = new QGridLayout; qviewer = new QViewerWidget(QRect(0, 0, ui->frame->width(), ui->frame->height())); layout->addWidget(qviewer); ui->frame->setLayout(layout); this->setCentralWidget(ui->frame);
حيث نقوم بإنشاء طبقة ، قم بإنشاء عنصر واجهة المستخدم الخاص بنا ، بأبعاد مساوية لحجم الإطار ، وأضف عنصر واجهة المستخدم الذي تم إنشاؤه إلى الطبقة ، وقم بإرفاق الطبقة بالإطار. ولكي لا تهتم بالتخطيط الموجود في هذا المثال ، فإننا نمد الإطار إلى منطقة العميل بأكملها من النافذة ، مما يجعله الأداة الرئيسية.
للتقديم ، يجب أن تقوم بترتيب التحديث الدوري للنافذة بواسطة المؤقت. للقيام بذلك ، قم بإنشاء جهاز ضبط وقت بفاصل 40 مللي ثانية (25 إطارًا في الثانية) وربط إشارة انتهاء الوقت بفتحة تحديث النافذة. أفعل ذلك بهذه الطريقة باستخدام بناء جملة Qt5
connect(&timer, &QTimer::timeout, this, &MainWindow::update); timer.start(40);
بعد تحديد فتحة التحديث لفئة النافذة بهذه الطريقة
void MainWindow::update() { QMainWindow::update(this->geometry()); }
لماذا ، لأنه يمكنك ربط إشارة المؤقت مباشرة بفتحة QMainWindow :: update بالطريقة نفسها التي تظهر بها معظم أمثلة osgQt
connect(&timer, SIGNAL(timeout), this, SLOT(update));
الحقيقة هي أنه تم إهمال بناء الجملة مع وحدات ماكرو SIGNAL () و SLOT () ، وتحسبًا للانتقال إلى Qt6 ، يجب التخلي عنه. في نفس الوقت ، فئة QMainWindow لا تحتوي على تحميل زائد لفتحة التحديث () بدون معلمات ، مما سيؤدي إلى حدوث خطأ في استدعاء الربط أثناء التحويل البرمجي. للقيام بذلك ، اضطررت إلى تحديد فتحة التحديث () الخاصة بي دون معلمات ، مع استدعاء القاعدة QMainWindow :: update () فيها ، لتمرير منطقة العميل في النافذة هناك.
إضافة إلى هذا المكان وتشغيل البرنامج ، سنحصل على نتيجة معينة

بالضغط على "S" ، يمكننا تفعيل شاشة إحصائيات OSG والتأكد من أن تطبيقنا يعمل كما ينبغي ، مع رسم مشهد فارغ.
أي نوع من الإحصاءات رصد؟حتى لا تفرط في المقالة سأكتب عنها هنا. لدى OSG شاشة مدمجة تعرض إحصائيات المحرك في الوقت الفعلي. لإضافته إلى العارض ، نقوم بتضمين ملف الرأس
#include <osgViewer/ViewerEventHandlers>
وأضف معالجًا للمشاهد
viewer.addEventHandler(new osgViewer::StatsHandler);
ثم في أي وقت عن طريق الضغط على "S" لعرض الكثير من المعلومات المفيدة.
4. إنهاء المشاهد لدينا: إضافة قائمة
في مصمم النماذج ، نقوم بتخصيص القائمة باستخدام البرمجة "الموجهة بالماوس" (التي أنا غير مبال بها ، ولكن نعم ، إنها مريحة أحيانًا). في النهاية سنحصل على شيء مثل هذا

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

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