├── .gitignore ├── .travis.yml ├── CHANGELOG.md ├── CMakeLists.txt ├── CONTRIBUTING.md ├── LICENSE.md ├── MainWindow.cpp ├── MainWindow.h ├── OSGWidget.cpp ├── OSGWidget.h ├── PickHandler.cpp ├── PickHandler.h ├── QtOSG.cpp ├── README.md └── qtosg.desktop /.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | 3 | *.swp 4 | *.user 5 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: required 2 | language: cpp 3 | dist: trusty 4 | 5 | compiler: 6 | - clang 7 | - gcc 8 | 9 | before_script: 10 | - sudo add-apt-repository --yes ppa:beineri/opt-qt591-trusty 11 | - sudo apt-get update -qq 12 | - sudo apt-get install qt59base qtbase5-dev libqt5opengl5-dev libopenscenegraph-dev 13 | 14 | script: 15 | - source /opt/qt59/bin/qt59-env.sh 16 | - mkdir build 17 | - cd build 18 | - cmake .. 19 | - make 20 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Version 0.9.9 2 | 3 | This version is almost ready for public consumption, apart from some 4 | known issues. 5 | 6 | Features: 7 | 8 | - Picking 9 | - Rectangular selection 10 | - Thread-safety (thread affinity) 11 | - Compatible with `Qt5` 12 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | CMAKE_MINIMUM_REQUIRED( VERSION 2.8.11 ) 2 | PROJECT( QtOSG ) 3 | 4 | if( NOT MSVC ) 5 | SET( CMAKE_CXX_FLAGS "-std=c++11 -Wall -pedantic -Wno-long-long" ) 6 | ENDIF() 7 | 8 | SET( CMAKE_INCLUDE_CURRENT_DIR ON ) 9 | SET( CMAKE_AUTOMOC ON ) 10 | 11 | # QT5 Handling - sample under OSX : cmake .. -DQT5_DIR=/usr/local/Cellar/qt/5.10.1 12 | IF( DEFINED ENV{QT5_DIR} ) 13 | SET( QT5_DIR $ENV{QT5_DIR} ) 14 | ENDIF() 15 | 16 | SET( CMAKE_PREFIX_PATH ${CMAKE_PREFIX_PATH} ${QT5_DIR} ) 17 | 18 | FIND_PACKAGE( Qt5Core ) 19 | FIND_PACKAGE( Qt5Gui ) 20 | FIND_PACKAGE( Qt5OpenGL ) 21 | FIND_PACKAGE( OpenSceneGraph REQUIRED COMPONENTS osgDB osgGA osgUtil osgViewer ) 22 | 23 | INCLUDE_DIRECTORIES( ${OPENSCENEGRAPH_INCLUDE_DIRS} ) 24 | 25 | SET( WITH_PICK_HANDLER ON CACHE BOOL "Build with pick handler support" ) 26 | SET( WITH_SELECTION_PROCESSING ON CACHE BOOL "Build with selection processing support" ) 27 | 28 | SET( SOURCES 29 | MainWindow.cpp 30 | OSGWidget.cpp 31 | PickHandler.cpp 32 | QtOSG.cpp 33 | ) 34 | 35 | ADD_EXECUTABLE( qtosg ${SOURCES} ) 36 | 37 | IF( WITH_PICK_HANDLER ) 38 | MESSAGE( STATUS "Building with pick handler support" ) 39 | 40 | SET_PROPERTY( 41 | TARGET qtosg 42 | APPEND PROPERTY COMPILE_DEFINITIONS WITH_PICK_HANDLER 43 | ) 44 | ENDIF() 45 | 46 | IF( WITH_SELECTION_PROCESSING ) 47 | MESSAGE( STATUS "Building with selection processing support" ) 48 | 49 | SET_PROPERTY( 50 | TARGET qtosg 51 | APPEND PROPERTY COMPILE_DEFINITIONS WITH_SELECTION_PROCESSING 52 | ) 53 | ENDIF() 54 | 55 | TARGET_LINK_LIBRARIES( qtosg 56 | ${OPENSCENEGRAPH_LIBRARIES} 57 | Qt5::Core 58 | Qt5::Gui 59 | Qt5::OpenGL 60 | ) 61 | 62 | IF( MSVC ) 63 | SET( QT_PLUGINS_DIR "${QT5_DIR}/plugins" ) 64 | 65 | ###################################################################### 66 | # Copy Qt plugins to 'Debug & Release' directories & configure qt.conf file 67 | ###################################################################### 68 | 69 | fILE( GLOB qtplugin_dirs RELATIVE 70 | "${QT_PLUGINS_DIR}" 71 | "${QT_PLUGINS_DIR}/imageformats*" 72 | "${QT_PLUGINS_DIR}/platforms*" 73 | ) 74 | FILE( REMOVE_RECURSE 75 | "${CMAKE_CURRENT_BINARY_DIR}/${EXECUTABLE_OUTPUT_PATH}/Debug/QtPlugins" 76 | ) 77 | 78 | FILE( REMOVE_RECURSE 79 | "${CMAKE_CURRENT_BINARY_DIR}/${EXECUTABLE_OUTPUT_PATH}/Release/QtPlugins" 80 | ) 81 | 82 | FILE( MAKE_DIRECTORY 83 | "${CMAKE_CURRENT_BINARY_DIR}/${EXECUTABLE_OUTPUT_PATH}/Debug/QtPlugins" 84 | ) 85 | 86 | FILE( MAKE_DIRECTORY 87 | "${CMAKE_CURRENT_BINARY_DIR}/${EXECUTABLE_OUTPUT_PATH}/Release/QtPlugins" 88 | ) 89 | 90 | FOREACH( qtplugin ${qtplugin_dirs} ) 91 | FILE( COPY "${QT_PLUGINS_DIR}/${qtplugin}" 92 | DESTINATION "${CMAKE_CURRENT_BINARY_DIR}/${EXECUTABLE_OUTPUT_PATH}/Debug/QtPlugins" 93 | ) 94 | FILE( COPY "${QT_PLUGINS_DIR}/${qtplugin}" 95 | DESTINATION "${CMAKE_CURRENT_BINARY_DIR}/${EXECUTABLE_OUTPUT_PATH}/Release/QtPlugins" 96 | ) 97 | ENDFOREACH() 98 | 99 | FILE( WRITE 100 | "${CMAKE_CURRENT_BINARY_DIR}/${EXECUTABLE_OUTPUT_PATH}/Debug/qt.conf" 101 | "[Paths]\nPlugins = QtPlugins" 102 | ) 103 | 104 | FILE( WRITE 105 | "${CMAKE_CURRENT_BINARY_DIR}/${EXECUTABLE_OUTPUT_PATH}/Release/qt.conf" 106 | "[Paths]\nPlugins = QtPlugins" 107 | ) 108 | ENDIF() 109 | 110 | INSTALL( TARGETS qtosg RUNTIME DESTINATION bin ) 111 | INSTALL( PROGRAMS qtosg.desktop DESTINATION share/applications ) 112 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributors 2 | 3 | The following people contributed code to `QtOSG`: 4 | 5 | * [fiesh](https://github.com/fiesh): thread affinity bugfixes 6 | * [mtola](https://github.com/mtola) (Martial Tola): CMake fixes for Mac OS 7 | X and Windows 8 | * [Submanifold](https://github.com/Submanifold) (Bastian Rieck): main 9 | contributor 10 | 11 | # How to contribute 12 | 13 | Missing a feature in the widget? Something does not work the way you 14 | expect it to work? Please open an [issue](https://github.com/Submanifold/QtOSG/issues) 15 | in the project's issue tracker. I am also more than happy to merge your 16 | [pull requests](https://github.com/Submanifold/QtOSG/pulls). 17 | 18 | Since this project is not really large, there are no coding conventions. 19 | If you want to make me happy, just emulate my coding style. If you don't 20 | like my coding style, I will just refactor your code prior/after 21 | pulling. Let's be excellent to each other! 22 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright (C) 2014 Bastian Rieck 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | this software and associated documentation files (the "Software"), to deal in 5 | the Software without restriction, including without limitation the rights to 6 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 7 | of the Software, and to permit persons to whom the Software is furnished to do 8 | so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | -------------------------------------------------------------------------------- /MainWindow.cpp: -------------------------------------------------------------------------------- 1 | #include "MainWindow.h" 2 | #include "OSGWidget.h" 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | MainWindow::MainWindow( QWidget* parent, Qt::WindowFlags flags ) 9 | : QMainWindow( parent, flags ), 10 | mdiArea_( new QMdiArea( this ) ) 11 | { 12 | QMenuBar* menuBar = this->menuBar(); 13 | 14 | QMenu* menu = menuBar->addMenu( "Test" ); 15 | menu->addAction( "Create view", this, SLOT( onCreateView() ) ); 16 | 17 | this->setCentralWidget( mdiArea_ ); 18 | } 19 | 20 | MainWindow::~MainWindow() 21 | { 22 | } 23 | 24 | void MainWindow::onCreateView() 25 | { 26 | OSGWidget* osgWidget = new OSGWidget( this ); 27 | QMdiSubWindow* subWindow = mdiArea_->addSubWindow( osgWidget ); 28 | 29 | subWindow->show(); 30 | } 31 | -------------------------------------------------------------------------------- /MainWindow.h: -------------------------------------------------------------------------------- 1 | #ifndef MainWindow_h__ 2 | #define MainWindow_h__ 3 | 4 | #include 5 | #include 6 | 7 | class MainWindow : public QMainWindow 8 | { 9 | Q_OBJECT 10 | 11 | public: 12 | MainWindow( QWidget* parent = 0, Qt::WindowFlags flags = 0 ); 13 | ~MainWindow(); 14 | 15 | private slots: 16 | void onCreateView(); 17 | 18 | private: 19 | QMdiArea* mdiArea_; 20 | }; 21 | 22 | #endif 23 | -------------------------------------------------------------------------------- /OSGWidget.cpp: -------------------------------------------------------------------------------- 1 | #include "OSGWidget.h" 2 | #include "PickHandler.h" 3 | 4 | #include 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | #include 14 | 15 | #include 16 | #include 17 | 18 | #include 19 | #include 20 | 21 | #include 22 | #include 23 | 24 | #include 25 | 26 | #include 27 | #include 28 | 29 | #include 30 | #include 31 | #include 32 | #include 33 | 34 | namespace 35 | { 36 | 37 | #ifdef WITH_SELECTION_PROCESSING 38 | QRect makeRectangle( const QPoint& first, const QPoint& second ) 39 | { 40 | // Relative to the first point, the second point may be in either one of the 41 | // four quadrants of an Euclidean coordinate system. 42 | // 43 | // We enumerate them in counter-clockwise order, starting from the lower-right 44 | // quadrant that corresponds to the default case: 45 | // 46 | // | 47 | // (3) | (4) 48 | // | 49 | // -------|------- 50 | // | 51 | // (2) | (1) 52 | // | 53 | 54 | if( second.x() >= first.x() && second.y() >= first.y() ) 55 | return QRect( first, second ); 56 | else if( second.x() < first.x() && second.y() >= first.y() ) 57 | return QRect( QPoint( second.x(), first.y() ), QPoint( first.x(), second.y() ) ); 58 | else if( second.x() < first.x() && second.y() < first.y() ) 59 | return QRect( second, first ); 60 | else if( second.x() >= first.x() && second.y() < first.y() ) 61 | return QRect( QPoint( first.x(), second.y() ), QPoint( second.x(), first.y() ) ); 62 | 63 | // Should never reach that point... 64 | return QRect(); 65 | } 66 | #endif 67 | 68 | } 69 | 70 | namespace osgWidget 71 | { 72 | void Viewer::setupThreading() 73 | { 74 | if( _threadingModel == SingleThreaded ) 75 | { 76 | if(_threadsRunning) 77 | stopThreading(); 78 | } 79 | else 80 | { 81 | if(!_threadsRunning) 82 | startThreading(); 83 | } 84 | } 85 | } 86 | 87 | OSGWidget::OSGWidget( QWidget* parent, 88 | Qt::WindowFlags f ) 89 | : QOpenGLWidget( parent, 90 | f ) 91 | , graphicsWindow_( new osgViewer::GraphicsWindowEmbedded( this->x(), 92 | this->y(), 93 | this->width(), 94 | this->height() ) ) 95 | , viewer_( new osgWidget::Viewer ) 96 | , selectionActive_( false ) 97 | , selectionFinished_( true ) 98 | { 99 | osg::Sphere* sphere = new osg::Sphere( osg::Vec3( 0.f, 0.f, 0.f ), 0.25f ); 100 | osg::ShapeDrawable* sd = new osg::ShapeDrawable( sphere ); 101 | sd->setColor( osg::Vec4( 1.f, 0.f, 0.f, 1.f ) ); 102 | sd->setName( "A nice sphere" ); 103 | 104 | osg::Geode* geode = new osg::Geode; 105 | geode->addDrawable( sd ); 106 | 107 | // Set material for basic lighting and enable depth tests. Else, the sphere 108 | // will suffer from rendering errors. 109 | { 110 | osg::StateSet* stateSet = geode->getOrCreateStateSet(); 111 | osg::Material* material = new osg::Material; 112 | 113 | material->setColorMode( osg::Material::AMBIENT_AND_DIFFUSE ); 114 | 115 | stateSet->setAttributeAndModes( material, osg::StateAttribute::ON ); 116 | stateSet->setMode( GL_DEPTH_TEST, osg::StateAttribute::ON ); 117 | } 118 | 119 | float aspectRatio = static_cast( this->width() / 2 ) / static_cast( this->height() ); 120 | auto pixelRatio = this->devicePixelRatio(); 121 | 122 | osg::Camera* camera = new osg::Camera; 123 | camera->setViewport( 0, 0, this->width() / 2 * pixelRatio, this->height() * pixelRatio ); 124 | camera->setClearColor( osg::Vec4( 0.f, 0.f, 1.f, 1.f ) ); 125 | camera->setProjectionMatrixAsPerspective( 30.f, aspectRatio, 1.f, 1000.f ); 126 | camera->setGraphicsContext( graphicsWindow_ ); 127 | 128 | osgViewer::View* view = new osgViewer::View; 129 | view->setCamera( camera ); 130 | view->setSceneData( geode ); 131 | view->addEventHandler( new osgViewer::StatsHandler ); 132 | #ifdef WITH_PICK_HANDLER 133 | view->addEventHandler( new PickHandler( this->devicePixelRatio() ) ); 134 | #endif 135 | 136 | osgGA::TrackballManipulator* manipulator = new osgGA::TrackballManipulator; 137 | manipulator->setAllowThrow( false ); 138 | 139 | view->setCameraManipulator( manipulator ); 140 | 141 | osg::Camera* sideCamera = new osg::Camera; 142 | sideCamera->setViewport( this->width() /2 * pixelRatio, 0, 143 | this->width() /2 * pixelRatio, this->height() * pixelRatio ); 144 | 145 | sideCamera->setClearColor( osg::Vec4( 0.f, 0.f, 1.f, 1.f ) ); 146 | sideCamera->setProjectionMatrixAsPerspective( 30.f, aspectRatio, 1.f, 1000.f ); 147 | sideCamera->setGraphicsContext( graphicsWindow_ ); 148 | 149 | osgViewer::View* sideView = new osgViewer::View; 150 | sideView->setCamera( sideCamera ); 151 | sideView->setSceneData( geode ); 152 | sideView->addEventHandler( new osgViewer::StatsHandler ); 153 | sideView->setCameraManipulator( new osgGA::TrackballManipulator ); 154 | 155 | viewer_->addView( view ); 156 | viewer_->addView( sideView ); 157 | viewer_->setThreadingModel( osgViewer::CompositeViewer::SingleThreaded ); 158 | viewer_->realize(); 159 | 160 | // This ensures that the widget will receive keyboard events. This focus 161 | // policy is not set by default. The default, Qt::NoFocus, will result in 162 | // keyboard events that are ignored. 163 | this->setFocusPolicy( Qt::StrongFocus ); 164 | this->setMinimumSize( 100, 100 ); 165 | 166 | // Ensures that the widget receives mouse move events even though no 167 | // mouse button has been pressed. We require this in order to let the 168 | // graphics window switch viewports properly. 169 | this->setMouseTracking( true ); 170 | } 171 | 172 | OSGWidget::~OSGWidget() 173 | { 174 | } 175 | 176 | void OSGWidget::paintEvent( QPaintEvent* /* paintEvent */ ) 177 | { 178 | this->makeCurrent(); 179 | 180 | QPainter painter( this ); 181 | painter.setRenderHint( QPainter::Antialiasing ); 182 | 183 | this->paintGL(); 184 | 185 | #ifdef WITH_SELECTION_PROCESSING 186 | if( selectionActive_ && !selectionFinished_ ) 187 | { 188 | painter.setPen( Qt::black ); 189 | painter.setBrush( Qt::transparent ); 190 | painter.drawRect( makeRectangle( selectionStart_, selectionEnd_ ) ); 191 | } 192 | #endif 193 | 194 | painter.end(); 195 | 196 | this->doneCurrent(); 197 | } 198 | 199 | void OSGWidget::paintGL() 200 | { 201 | viewer_->frame(); 202 | } 203 | 204 | void OSGWidget::resizeGL( int width, int height ) 205 | { 206 | this->getEventQueue()->windowResize( this->x(), this->y(), width, height ); 207 | graphicsWindow_->resized( this->x(), this->y(), width, height ); 208 | 209 | this->onResize( width, height ); 210 | } 211 | 212 | void OSGWidget::keyPressEvent( QKeyEvent* event ) 213 | { 214 | QString keyString = event->text(); 215 | const char* keyData = keyString.toLocal8Bit().data(); 216 | 217 | if( event->key() == Qt::Key_S ) 218 | { 219 | #ifdef WITH_SELECTION_PROCESSING 220 | selectionActive_ = !selectionActive_; 221 | #endif 222 | 223 | // Further processing is required for the statistics handler here, so we do 224 | // not return right away. 225 | } 226 | else if( event->key() == Qt::Key_D ) 227 | { 228 | osgDB::writeNodeFile( *viewer_->getView(0)->getSceneData(), 229 | "/tmp/sceneGraph.osg" ); 230 | 231 | return; 232 | } 233 | else if( event->key() == Qt::Key_H ) 234 | { 235 | this->onHome(); 236 | return; 237 | } 238 | 239 | this->getEventQueue()->keyPress( osgGA::GUIEventAdapter::KeySymbol( *keyData ) ); 240 | } 241 | 242 | void OSGWidget::keyReleaseEvent( QKeyEvent* event ) 243 | { 244 | QString keyString = event->text(); 245 | const char* keyData = keyString.toLocal8Bit().data(); 246 | 247 | this->getEventQueue()->keyRelease( osgGA::GUIEventAdapter::KeySymbol( *keyData ) ); 248 | } 249 | 250 | void OSGWidget::mouseMoveEvent( QMouseEvent* event ) 251 | { 252 | // Note that we have to check the buttons mask in order to see whether the 253 | // left button has been pressed. A call to `button()` will only result in 254 | // `Qt::NoButton` for mouse move events. 255 | if( selectionActive_ && event->buttons() & Qt::LeftButton ) 256 | { 257 | selectionEnd_ = event->pos(); 258 | 259 | // Ensures that new paint events are created while the user moves the 260 | // mouse. 261 | this->update(); 262 | } 263 | else 264 | { 265 | auto pixelRatio = this->devicePixelRatio(); 266 | 267 | this->getEventQueue()->mouseMotion( static_cast( event->x() * pixelRatio ), 268 | static_cast( event->y() * pixelRatio ) ); 269 | } 270 | } 271 | 272 | void OSGWidget::mousePressEvent( QMouseEvent* event ) 273 | { 274 | // Selection processing 275 | if( selectionActive_ && event->button() == Qt::LeftButton ) 276 | { 277 | selectionStart_ = event->pos(); 278 | selectionEnd_ = selectionStart_; // Deletes the old selection 279 | selectionFinished_ = false; // As long as this is set, the rectangle will be drawn 280 | } 281 | 282 | // Normal processing 283 | else 284 | { 285 | // 1 = left mouse button 286 | // 2 = middle mouse button 287 | // 3 = right mouse button 288 | 289 | unsigned int button = 0; 290 | 291 | switch( event->button() ) 292 | { 293 | case Qt::LeftButton: 294 | button = 1; 295 | break; 296 | 297 | case Qt::MiddleButton: 298 | button = 2; 299 | break; 300 | 301 | case Qt::RightButton: 302 | button = 3; 303 | break; 304 | 305 | default: 306 | break; 307 | } 308 | 309 | auto pixelRatio = this->devicePixelRatio(); 310 | 311 | this->getEventQueue()->mouseButtonPress( static_cast( event->x() * pixelRatio ), 312 | static_cast( event->y() * pixelRatio ), 313 | button ); 314 | } 315 | } 316 | 317 | void OSGWidget::mouseReleaseEvent(QMouseEvent* event) 318 | { 319 | // Selection processing: Store end position and obtain selected objects 320 | // through polytope intersection. 321 | if( selectionActive_ && event->button() == Qt::LeftButton ) 322 | { 323 | selectionEnd_ = event->pos(); 324 | selectionFinished_ = true; // Will force the painter to stop drawing the 325 | // selection rectangle 326 | 327 | this->processSelection(); 328 | } 329 | 330 | // Normal processing 331 | else 332 | { 333 | // 1 = left mouse button 334 | // 2 = middle mouse button 335 | // 3 = right mouse button 336 | 337 | unsigned int button = 0; 338 | 339 | switch( event->button() ) 340 | { 341 | case Qt::LeftButton: 342 | button = 1; 343 | break; 344 | 345 | case Qt::MiddleButton: 346 | button = 2; 347 | break; 348 | 349 | case Qt::RightButton: 350 | button = 3; 351 | break; 352 | 353 | default: 354 | break; 355 | } 356 | 357 | auto pixelRatio = this->devicePixelRatio(); 358 | 359 | this->getEventQueue()->mouseButtonRelease( static_cast( pixelRatio * event->x() ), 360 | static_cast( pixelRatio * event->y() ), 361 | button ); 362 | } 363 | } 364 | 365 | void OSGWidget::wheelEvent( QWheelEvent* event ) 366 | { 367 | // Ignore wheel events as long as the selection is active. 368 | if( selectionActive_ ) 369 | return; 370 | 371 | event->accept(); 372 | int delta = event->delta(); 373 | 374 | osgGA::GUIEventAdapter::ScrollingMotion motion = delta > 0 ? osgGA::GUIEventAdapter::SCROLL_UP 375 | : osgGA::GUIEventAdapter::SCROLL_DOWN; 376 | 377 | this->getEventQueue()->mouseScroll( motion ); 378 | } 379 | 380 | bool OSGWidget::event( QEvent* event ) 381 | { 382 | bool handled = QOpenGLWidget::event( event ); 383 | 384 | // This ensures that the OSG widget is always going to be repainted after the 385 | // user performed some interaction. Doing this in the event handler ensures 386 | // that we don't forget about some event and prevents duplicate code. 387 | switch( event->type() ) 388 | { 389 | case QEvent::KeyPress: 390 | case QEvent::KeyRelease: 391 | case QEvent::MouseButtonDblClick: 392 | case QEvent::MouseButtonPress: 393 | case QEvent::MouseButtonRelease: 394 | case QEvent::MouseMove: 395 | case QEvent::Wheel: 396 | this->update(); 397 | break; 398 | 399 | default: 400 | break; 401 | } 402 | 403 | return handled; 404 | } 405 | 406 | void OSGWidget::onHome() 407 | { 408 | osgViewer::ViewerBase::Views views; 409 | viewer_->getViews( views ); 410 | 411 | for( std::size_t i = 0; i < views.size(); i++ ) 412 | { 413 | osgViewer::View* view = views.at(i); 414 | view->home(); 415 | } 416 | } 417 | 418 | void OSGWidget::onResize( int width, int height ) 419 | { 420 | std::vector cameras; 421 | viewer_->getCameras( cameras ); 422 | 423 | assert( cameras.size() == 2 ); 424 | 425 | auto pixelRatio = this->devicePixelRatio(); 426 | 427 | cameras[0]->setViewport( 0, 0, width / 2 * pixelRatio, height * pixelRatio ); 428 | cameras[1]->setViewport( width / 2 * pixelRatio, 0, width / 2 * pixelRatio, height * pixelRatio ); 429 | } 430 | 431 | osgGA::EventQueue* OSGWidget::getEventQueue() const 432 | { 433 | osgGA::EventQueue* eventQueue = graphicsWindow_->getEventQueue(); 434 | 435 | if( eventQueue ) 436 | return eventQueue; 437 | else 438 | throw std::runtime_error( "Unable to obtain valid event queue"); 439 | } 440 | 441 | void OSGWidget::processSelection() 442 | { 443 | #ifdef WITH_SELECTION_PROCESSING 444 | QRect selectionRectangle = makeRectangle( selectionStart_, selectionEnd_ ); 445 | auto widgetHeight = this->height(); 446 | auto pixelRatio = this->devicePixelRatio(); 447 | 448 | double xMin = selectionRectangle.left(); 449 | double xMax = selectionRectangle.right(); 450 | double yMin = widgetHeight - selectionRectangle.bottom(); 451 | double yMax = widgetHeight - selectionRectangle.top(); 452 | 453 | xMin *= pixelRatio; 454 | yMin *= pixelRatio; 455 | xMax *= pixelRatio; 456 | yMax *= pixelRatio; 457 | 458 | osgUtil::PolytopeIntersector* polytopeIntersector 459 | = new osgUtil::PolytopeIntersector( osgUtil::PolytopeIntersector::WINDOW, 460 | xMin, yMin, 461 | xMax, yMax ); 462 | 463 | // This limits the amount of intersections that are reported by the 464 | // polytope intersector. Using this setting, a single drawable will 465 | // appear at most once while calculating intersections. This is the 466 | // preferred and expected behaviour. 467 | polytopeIntersector->setIntersectionLimit( osgUtil::Intersector::LIMIT_ONE_PER_DRAWABLE ); 468 | 469 | osgUtil::IntersectionVisitor iv( polytopeIntersector ); 470 | 471 | for( unsigned int viewIndex = 0; viewIndex < viewer_->getNumViews(); viewIndex++ ) 472 | { 473 | qDebug() << "View index:" << viewIndex; 474 | 475 | osgViewer::View* view = viewer_->getView( viewIndex ); 476 | 477 | if( !view ) 478 | throw std::runtime_error( "Unable to obtain valid view for selection processing" ); 479 | 480 | osg::Camera* camera = view->getCamera(); 481 | 482 | if( !camera ) 483 | throw std::runtime_error( "Unable to obtain valid camera for selection processing" ); 484 | 485 | camera->accept( iv ); 486 | 487 | if( !polytopeIntersector->containsIntersections() ) 488 | continue; 489 | 490 | auto intersections = polytopeIntersector->getIntersections(); 491 | 492 | for( auto&& intersection : intersections ) 493 | qDebug() << "Selected a drawable:" << QString::fromStdString( intersection.drawable->getName() ); 494 | } 495 | #endif 496 | } 497 | -------------------------------------------------------------------------------- /OSGWidget.h: -------------------------------------------------------------------------------- 1 | #ifndef OSGWidget_h__ 2 | #define OSGWidget_h__ 3 | 4 | #include 5 | #include 6 | 7 | #include 8 | 9 | #include 10 | #include 11 | 12 | namespace osgWidget 13 | { 14 | //! The subclass of osgViewer::CompositeViewer we use 15 | /*! 16 | * This subclassing allows us to remove the annoying automatic 17 | * setting of the CPU affinity to core 0 by osgViewer::ViewerBase, 18 | * osgViewer::CompositeViewer's base class. 19 | */ 20 | class Viewer : public osgViewer::CompositeViewer 21 | { 22 | public: 23 | virtual void setupThreading(); 24 | }; 25 | } 26 | 27 | class OSGWidget : public QOpenGLWidget 28 | { 29 | Q_OBJECT 30 | 31 | public: 32 | OSGWidget( QWidget* parent = 0, 33 | Qt::WindowFlags f = 0 ); 34 | 35 | virtual ~OSGWidget(); 36 | 37 | protected: 38 | 39 | virtual void paintEvent( QPaintEvent* paintEvent ); 40 | virtual void paintGL(); 41 | virtual void resizeGL( int width, int height ); 42 | 43 | virtual void keyPressEvent( QKeyEvent* event ); 44 | virtual void keyReleaseEvent( QKeyEvent* event ); 45 | 46 | virtual void mouseMoveEvent( QMouseEvent* event ); 47 | virtual void mousePressEvent( QMouseEvent* event ); 48 | virtual void mouseReleaseEvent( QMouseEvent* event ); 49 | virtual void wheelEvent( QWheelEvent* event ); 50 | 51 | virtual bool event( QEvent* event ); 52 | 53 | private: 54 | 55 | virtual void onHome(); 56 | virtual void onResize( int width, int height ); 57 | 58 | osgGA::EventQueue* getEventQueue() const; 59 | 60 | osg::ref_ptr graphicsWindow_; 61 | osg::ref_ptr viewer_; 62 | 63 | QPoint selectionStart_; 64 | QPoint selectionEnd_; 65 | 66 | bool selectionActive_; 67 | bool selectionFinished_; 68 | 69 | void processSelection(); 70 | }; 71 | 72 | #endif 73 | -------------------------------------------------------------------------------- /PickHandler.cpp: -------------------------------------------------------------------------------- 1 | #include "PickHandler.h" 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | 8 | #include 9 | 10 | #include 11 | 12 | PickHandler::PickHandler( double devicePixelRatio ) 13 | : devicePixelRatio_( devicePixelRatio ) 14 | { 15 | } 16 | 17 | PickHandler::~PickHandler() 18 | { 19 | } 20 | 21 | bool PickHandler::handle( const osgGA::GUIEventAdapter& ea, osgGA::GUIActionAdapter& aa ) 22 | { 23 | if( ea.getEventType() != osgGA::GUIEventAdapter::RELEASE && 24 | ea.getButton() != osgGA::GUIEventAdapter::LEFT_MOUSE_BUTTON ) 25 | { 26 | return false; 27 | } 28 | 29 | osgViewer::View* viewer = dynamic_cast( &aa ); 30 | 31 | if( viewer ) 32 | { 33 | osgUtil::LineSegmentIntersector* intersector 34 | = new osgUtil::LineSegmentIntersector( osgUtil::Intersector::WINDOW, ea.getX() * devicePixelRatio_, ea.getY() * devicePixelRatio_ ); 35 | 36 | osgUtil::IntersectionVisitor iv( intersector ); 37 | 38 | osg::Camera* camera = viewer->getCamera(); 39 | if( !camera ) 40 | return false; 41 | 42 | camera->accept( iv ); 43 | 44 | if( !intersector->containsIntersections() ) 45 | return false; 46 | 47 | auto intersections = intersector->getIntersections(); 48 | 49 | std::cout << "Got " << intersections.size() << " intersections:\n"; 50 | 51 | for( auto&& intersection : intersections ) 52 | std::cout << " - Local intersection point = " << intersection.localIntersectionPoint << "\n"; 53 | } 54 | 55 | return true; 56 | } 57 | -------------------------------------------------------------------------------- /PickHandler.h: -------------------------------------------------------------------------------- 1 | #ifndef PickHandler_h__ 2 | #define PickHandler_h__ 3 | 4 | #include 5 | 6 | class PickHandler : public osgGA::GUIEventHandler 7 | { 8 | public: 9 | PickHandler( double devicePixelRatio = 1.0 ); 10 | virtual ~PickHandler(); 11 | 12 | virtual bool handle( const osgGA::GUIEventAdapter& ea, 13 | osgGA::GUIActionAdapter& aa ); 14 | 15 | private: 16 | double devicePixelRatio_; 17 | }; 18 | 19 | #endif 20 | -------------------------------------------------------------------------------- /QtOSG.cpp: -------------------------------------------------------------------------------- 1 | #include "MainWindow.h" 2 | 3 | #include 4 | #include 5 | 6 | int main( int argc, char** argv ) 7 | { 8 | QApplication application( argc, argv ); 9 | 10 | QSurfaceFormat format; 11 | format.setVersion(2, 1); 12 | format.setProfile( QSurfaceFormat::CompatibilityProfile ); 13 | 14 | QSurfaceFormat::setDefaultFormat(format); 15 | 16 | MainWindow mainWindow; 17 | mainWindow.show(); 18 | 19 | return( application.exec() ); 20 | } 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://travis-ci.org/Submanifold/QtOSG.svg?branch=master)](https://travis-ci.org/Submanifold/QtOSG) [![CII Best Practices](https://bestpractices.coreinfrastructure.org/projects/1074/badge)](https://bestpractices.coreinfrastructure.org/projects/1074) 2 | 3 | # QtOSG: combining Qt and OSG in a thread-safe manner 4 | 5 | This repository contains a widget based on `QOpenGLWidget` that is able 6 | to wrap a viewer from the [OpenSceneGraph](http://www.openscenegraph.org) library. 7 | This makes it possible to combine both toolkits in a thread-safe manner. 8 | 9 | Moreover, the widget demonstrates several interaction mechanisms: 10 | 11 | 1. Rectangular selection processing 12 | 2. Pick handling 13 | 3. Node file writing (for debug purposes) 14 | 15 | # Requirements 16 | 17 | * Linux or MacOS X (see below for a brief discussion on supporting 18 | Microsoft Windows) 19 | * A recent C++ compiler with support for C++11 20 | * `CMake` (minimum version 2.8.11) 21 | * `Qt5` 22 | * `OpenSceneGraph` 23 | 24 | ## What about Microsoft Windows 25 | 26 | I am not familiar enough with graphics programming for Microsoft Windows 27 | to fully support this as a target platform. In [issue 7](https://github.com/Submanifold/QtOSG/issues/7), it was briefly 28 | discussed that some modifications are required for QtOSG to fully 29 | compile/work under Microsoft Windows. If you want to target this 30 | platform, I would be glad for the help! 31 | 32 | Thanks to [Martial Tola](https://github.com/mtola), support for 33 | compiling under Microsoft Windows has been added to the project 34 | and I am looking forward to any comments. 35 | 36 | # Building QtOSG 37 | 38 | $ git clone https://github.com/Submanifold/QtOSG 39 | $ cd QtOSG 40 | $ mkdir build 41 | $ cd build 42 | $ cmake ../ 43 | $ make 44 | 45 | Additional build options can be configured by issuing `ccmake .` in the 46 | build directory. In particular, you can toggle the following options: 47 | 48 | * `WITH_PICK_HANDLER`: toggle to compile with/without support for point 49 | picking 50 | * `WITH_SELECTION_PROCESSING`: toggle to compile with/without support 51 | for rectangular selections 52 | 53 | # Using QtOSG 54 | 55 | The demo application merely demonstrates basic usage of the widget. 56 | Don't expect too much! Start the application by issuing `./qtosg` in the 57 | build directory. 58 | 59 | The following key bindings are active: 60 | 61 | * `d`: writes the current scene graph to `/tmp/sceneGraph.osg` 62 | * `h`: resets the view to home 63 | * `s`: toggles selection processing (if compiled); if active, hold left 64 | mouse button down to draw a selection rectangle; selected objects will 65 | be shown on the console 66 | 67 | # How to contribute 68 | 69 | Please see the [contribution guidelines](CONTRIBUTING.md) for more 70 | information and a list of the contributors. 71 | 72 | # Licence 73 | 74 | `QtOSG` uses the MIT licence. Pleas see the file 75 | [LICENSE.md](LICENSE.md) in the main directory of the repository for 76 | more information. 77 | -------------------------------------------------------------------------------- /qtosg.desktop: -------------------------------------------------------------------------------- 1 | [Desktop Entry] 2 | 3 | Type=Application 4 | Version=1.0 5 | Name=qtosg 6 | Comment=A widget for integrating OpenSceneGraph and Qt widgets 7 | Exec=qtosg 8 | Terminal=false 9 | Categories=Utilities 10 | --------------------------------------------------------------------------------