From c7effd2498a56c626f58d9c9b37921395207efea Mon Sep 17 00:00:00 2001 From: dushyant basson <39452934+dushyant-basson@users.noreply.github.com> Date: Tue, 5 Sep 2023 17:51:25 +0530 Subject: [PATCH] Implemented: Application icons; OpenSceneGraph to handle OpenGL with Qt; devicePixelRatio() for handing render on highDPI displays; Sample 3D with handler; MouseHandler not yet working as expected (WIP) --- generate.sh | 1 + src/qtviewer/CMakeLists.txt | 18 +++- src/qtviewer/IfcViewerWidget.cpp | 165 +++++++++++++++++++++++++++++-- src/qtviewer/IfcViewerWidget.h | 30 +++++- src/qtviewer/MainWindow.cpp | 14 +-- src/qtviewer/MainWindow.h | 8 +- src/qtviewer/MessageLogger.h | 40 ++++++++ src/qtviewer/MouseHandler.cpp | 45 +++++++++ src/qtviewer/MouseHandler.h | 16 +++ src/qtviewer/ParseIfcFile.cpp | 14 +-- src/qtviewer/ParseIfcFile.h | 10 +- src/qtviewer/main.cpp | 5 +- 12 files changed, 321 insertions(+), 45 deletions(-) create mode 100644 src/qtviewer/MessageLogger.h create mode 100644 src/qtviewer/MouseHandler.cpp create mode 100644 src/qtviewer/MouseHandler.h diff --git a/generate.sh b/generate.sh index 73ce2689ae..13f0135d05 100755 --- a/generate.sh +++ b/generate.sh @@ -12,4 +12,5 @@ cmake ../cmake \ -DCOLLADA_SUPPORT=OFF \ -DBUILD_QTVIEWER=ON \ -DQT_DIR=/opt/homebrew/Cellar/qt/6.5.1_2 \ + -DOSG_DIR=/opt/homebrew/Cellar/open-scene-graph/3.6.5_2 \ -DCMAKE_EXPORT_COMPILE_COMMANDS=true diff --git a/src/qtviewer/CMakeLists.txt b/src/qtviewer/CMakeLists.txt index 6d7b9c4f4a..2bd0267a3b 100644 --- a/src/qtviewer/CMakeLists.txt +++ b/src/qtviewer/CMakeLists.txt @@ -31,6 +31,17 @@ if(Qt${QT_VERSION}_FOUND) message(STATUS "Found Qt Version: ${Qt${QT_VERSION}_VERSION}") endif() +message(STATUS "OSG_DIR: ${OSG_DIR}") +set(OSG_COMPONENTS osgDB osgGA osgUtil osgViewer CACHE STRING "OSG components") + +# Do not add "PATHS " in find_package for OpenSceneGraph CMake config to work +# on appleSiliconMacOS + Homebrew +find_package(OpenSceneGraph COMPONENTS ${OSG_COMPONENTS} REQUIRED) + +if(OPENSCENEGRAPH_FOUND) + message(STATUS "Found OpenSceneGraph Version: ${OPENSCENEGRAPH_VERSION}") +endif() + set(targetName "QtViewer") # Scan for .qrc files and add to build process @@ -52,6 +63,9 @@ set_source_files_properties(${app_icon_macos} PROPERTIES MACOSX_PACKAGE_LOCATION "Resources") add_executable(${targetName} + MessageLogger.h + MouseHandler.h + MouseHandler.cpp IfcViewerWidget.h IfcViewerWidget.cpp ParseIfcFile.h @@ -72,16 +86,18 @@ set_target_properties(${targetName} PROPERTIES target_link_libraries(${targetName} ${IFCOPENSHELL_LIBRARIES} + ${OPENCASCADE_LIBRARIES} + ${OPENSCENEGRAPH_LIBRARIES} Qt${QT_VERSION}::Core Qt${QT_VERSION}::Gui Qt${QT_VERSION}::OpenGL Qt${QT_VERSION}::OpenGLWidgets Qt${QT_VERSION}::Widgets - ${OPENCASCADE_LIBRARIES} ) target_include_directories(${targetName} PUBLIC ${QT_DIR}/include + ${OPENSCENEGRAPH_INCLUDE_DIRS} ) get_target_property(targetIncludeDirs ${targetName} INCLUDE_DIRECTORIES) diff --git a/src/qtviewer/IfcViewerWidget.cpp b/src/qtviewer/IfcViewerWidget.cpp index c2a77bed83..3d1c928617 100644 --- a/src/qtviewer/IfcViewerWidget.cpp +++ b/src/qtviewer/IfcViewerWidget.cpp @@ -1,27 +1,172 @@ #include "IfcViewerWidget.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "MessageLogger.h" +#include "MouseHandler.h" +#include "osg/ref_ptr" -IfcViewerWidget::IfcViewerWidget(QWidget *parent) - : QOpenGLWidget(parent) -{} +IfcViewerWidget::IfcViewerWidget(qreal dpiScale, QWidget *parent) : + QOpenGLWidget(parent), + m_dpiScale(dpiScale), + m_graphicsWindow(new osgViewer::GraphicsWindowEmbedded( + this->x(), this->y(), + this->width(), this->height() + )), + m_viewer(new osgViewer::Viewer), + root(new osg::Group) +{ } void IfcViewerWidget::initializeGL() { // Set up the rendering context, load shaders and other resources, etc.: - QOpenGLFunctions *f = QOpenGLContext::currentContext()->functions(); - f->glClearColor(1.0f, 1.0f, 1.0f, 1.0f); + + MessageLogger::log("devicePixelRatio: " + std::to_string(m_dpiScale)); + + this->buildSceneData(); + + // Set the root node as the scene data for the viewer + m_viewer->setSceneData(root); + + osg::StateSet* state = root->getOrCreateStateSet(); + state->setMode(GL_DEPTH_TEST, osg::StateAttribute::ON); + + m_viewer->getCamera()->setViewport(0, 0, this->width(), this->height()); + m_viewer->getCamera()->setClearColor(osg::Vec4(0.8f, 0.8f, 0.8f, 1.0f)); + m_viewer->getCamera()->setGraphicsContext(m_graphicsWindow); + + // Add event handlers + + this->setMouseTracking(false); + + //osg::ref_ptr trackballManipulator = new osgGA::TrackballManipulator; + //trackballManipulator->setAllowThrow(false); + //m_viewer->setCameraManipulator(trackballManipulator); + + osg::ref_ptr mouseHandler = new MouseHandler(m_viewer); + mouseHandler->setAllowThrow(false); + m_viewer->setCameraManipulator(mouseHandler); + + m_viewer->setThreadingModel(osgViewer::Viewer::SingleThreaded); + m_viewer->realize(); } void IfcViewerWidget::resizeGL(int w, int h) { - // Update projection matrix and other size related settings: - m_projection.setToIdentity(); - m_projection.perspective(45.0f, w / float(h), 0.01f, 100.0f); + this->getEventQueue()->windowResize(this->x() * m_dpiScale, this->y() * m_dpiScale, w * m_dpiScale, h * m_dpiScale); + m_graphicsWindow->resized(this->x() * m_dpiScale, this->y() * m_dpiScale, w * m_dpiScale, h * m_dpiScale); + + m_viewer->getCamera()->setViewport(0, 0, this->width() * m_dpiScale, this->height() * m_dpiScale); + + const float aspectRatio = static_cast(w) / static_cast(h); + m_viewer->getCamera()->setProjectionMatrixAsPerspective(30.0f, aspectRatio, 1.0f, 1000.0f); } void IfcViewerWidget::paintGL() { // Render geometries from the parsed IFC file // Draw the scene: - QOpenGLFunctions *f = QOpenGLContext::currentContext()->functions(); - f->glClear(GL_COLOR_BUFFER_BIT); + //QOpenGLFunctions *f = QOpenGLContext::currentContext()->functions(); + //f->glClear(GL_COLOR_BUFFER_BIT); + + //MessageLogger::log("paintGL called"); + + glClear(GL_COLOR_BUFFER_BIT); + //glClearColor(0.2f, 0.6f, 0.9f, 0.5f); + + // Render OSG scene + m_viewer->frame(); +} + +void IfcViewerWidget::mouseMoveEvent(QMouseEvent *event) +{ + this->getEventQueue()->mouseMotion(event->position().x() * m_dpiScale, event->position().y() * m_dpiScale); +} + +void IfcViewerWidget::mousePressEvent(QMouseEvent *event) +{ + unsigned int button = this->getMouseButtonNum(event); + this->getEventQueue()->mouseButtonPress(event->position().x() * m_dpiScale, event->position().y() * m_dpiScale, button); +} + +void IfcViewerWidget::mouseReleaseEvent(QMouseEvent *event) +{ + unsigned int button = this->getMouseButtonNum(event); + this->getEventQueue()->mouseButtonRelease(event->position().x() * m_dpiScale, event->position().y() * m_dpiScale, button); +} + +void IfcViewerWidget::wheelEvent(QWheelEvent *event) +{ + int delta = event->angleDelta().y(); + osgGA::GUIEventAdapter::ScrollingMotion motion = delta > 0 ? + osgGA::GUIEventAdapter::SCROLL_UP : osgGA::GUIEventAdapter::SCROLL_DOWN; + this->getEventQueue()->mouseScroll(motion); +} + +bool IfcViewerWidget::event(QEvent* event) +{ + bool handled = QOpenGLWidget::event(event); + this->update(); + return handled; +} + +osgGA::EventQueue* IfcViewerWidget::getEventQueue() const +{ + osgGA::EventQueue* eventQueue = m_graphicsWindow->getEventQueue(); + return eventQueue; +} + +unsigned int IfcViewerWidget::getMouseButtonNum(QMouseEvent* event) +{ + unsigned int button = 0; + switch (event->button()){ + case Qt::LeftButton: + button = 1; + break; + case Qt::MiddleButton: + button = 2; + break; + case Qt::RightButton: + button = 3; + break; + default: + break; + } + return button; +} + +void IfcViewerWidget::buildSceneData() +{ + // Cube + osg::ref_ptr box = new osg::Box(osg::Vec3(0, 0, 0), 1.0f); + osg::ref_ptr shapeDrawable = new osg::ShapeDrawable(box); + + // Set material properties (optional) + osg::ref_ptr material = new osg::Material; + material->setDiffuse(osg::Material::FRONT_AND_BACK, osg::Vec4(1.0f, 0.0f, 0.0f, 1.0f)); // Red color + shapeDrawable->getOrCreateStateSet()->setAttributeAndModes(material.get()); + + osg::ref_ptr geode = new osg::Geode; + geode->addDrawable(shapeDrawable); + + // Apply a rotation to the cube to make it 3D + osg::ref_ptr cubeTransform = new osg::MatrixTransform; + osg::ref_ptr rotationTransform = new osg::MatrixTransform; + rotationTransform->setMatrix(osg::Matrix::rotate(osg::DegreesToRadians(45.0), osg::Vec3(1.0, 1.0, 0.0))); + cubeTransform->addChild(geode); + rotationTransform->addChild(cubeTransform); + + // Translate the cube to a proper position + osg::ref_ptr translationTransform = new osg::MatrixTransform; + translationTransform->setMatrix(osg::Matrix::translate(osg::Vec3(0.0, 0.0, -5.0))); + translationTransform->addChild(rotationTransform); + + // Add the cube to the root node + root->addChild(translationTransform); } diff --git a/src/qtviewer/IfcViewerWidget.h b/src/qtviewer/IfcViewerWidget.h index 8ce7937069..93bce7690b 100644 --- a/src/qtviewer/IfcViewerWidget.h +++ b/src/qtviewer/IfcViewerWidget.h @@ -6,18 +6,38 @@ #include #include +#include +#include +#include +#include + class IfcViewerWidget : public QOpenGLWidget { + public: - IfcViewerWidget(QWidget *parent = nullptr); + IfcViewerWidget(qreal dpiScale, QWidget *parent = nullptr); protected: - void initializeGL() override; - void resizeGL(int w, int h) override; - void paintGL() override; + virtual void initializeGL() override; // to set up resources, state + virtual void resizeGL(int w, int h) override; // to set up viewport, projection, etc. + virtual void paintGL() override; // to render OpenGL scene + + virtual void mousePressEvent(QMouseEvent *event) override; + virtual void mouseMoveEvent(QMouseEvent *event) override; + virtual void mouseReleaseEvent(QMouseEvent *event) override; + virtual void wheelEvent(QWheelEvent *event) override; + virtual bool event(QEvent* event) override; private: + qreal m_dpiScale; QMatrix4x4 m_projection; + osg::ref_ptr m_graphicsWindow; + osg::ref_ptr m_viewer; + osg::ref_ptr root; // OSG root node + + osgGA::EventQueue* getEventQueue() const; + unsigned int getMouseButtonNum(QMouseEvent* event); + void buildSceneData(); }; -#endif // IFCVIEWERWIDGET_H \ No newline at end of file +#endif // IFCVIEWERWIDGET_H diff --git a/src/qtviewer/MainWindow.cpp b/src/qtviewer/MainWindow.cpp index d7f1189a10..200c372de6 100644 --- a/src/qtviewer/MainWindow.cpp +++ b/src/qtviewer/MainWindow.cpp @@ -1,4 +1,4 @@ -#include +#include #include #include #include @@ -10,15 +10,16 @@ #include #include "MainWindow.h" +#include "MessageLogger.h" #include "IfcViewerWidget.h" -MainWindow::MainWindow(QWidget *parent) - : QMainWindow(parent) +MainWindow::MainWindow(qreal dpiScale, QWidget *parent) + : QMainWindow(parent), m_dpiScale(dpiScale) { this->setWindowTitle("IfcOpenShell Viewer"); this->resize(800, 600); //temporary reasonable initial size - m_glWidget = new IfcViewerWidget(this); + m_glWidget = new IfcViewerWidget(m_dpiScale, this); QSizePolicy glSizePolicy = m_glWidget->sizePolicy(); glSizePolicy.setVerticalStretch(3); m_glWidget->setSizePolicy(glSizePolicy); @@ -81,7 +82,8 @@ void MainWindow::createConnections() //connect(m_backgroundAction, SIGNAL(toggled(bool)), (QWidget*)m_v, SLOT(setViewBackground(bool))); //connect(m_outlineAction, SIGNAL(toggled(bool)), (QWidget*)m_v, SLOT(setViewOutline(bool))); - connect(&m_parser, &ParseIfcFile::parsingInfo, this, &MainWindow::appendToOutputText); + //connect(&m_parser, &ParseIfcFile::parsingInfo, this, &MainWindow::appendToOutputText); + connect(&MessageLogger::getInstance(), SIGNAL(logMessage(QString)), this, SLOT(appendToOutputText(QString))); } void MainWindow::appendToOutputText(const QString& message) @@ -124,4 +126,4 @@ void MainWindow::openFile() m_outlineAction->setEnabled(true); m_backgroundAction->setEnabled(true); -} \ No newline at end of file +} diff --git a/src/qtviewer/MainWindow.h b/src/qtviewer/MainWindow.h index 2cd41e1ca2..34062eee4a 100644 --- a/src/qtviewer/MainWindow.h +++ b/src/qtviewer/MainWindow.h @@ -1,4 +1,4 @@ -#ifndef MAINWINDOW_H +#ifndef MAINWINDOW_H #define MAINWINDOW_H #include @@ -15,17 +15,19 @@ class MainWindow : public QMainWindow Q_OBJECT public: - MainWindow(QWidget *parent = nullptr); + MainWindow(qreal dpiScale, QWidget *parent = nullptr); public slots: void openFile(); + void appendToOutputText(const QString& message); private: void createActions(); void createMenus(); void createConnections(); - void appendToOutputText(const QString& message); private: + qreal m_dpiScale; + QMenu *fileMenu; QAction *openAction; QAction *quitAction; diff --git a/src/qtviewer/MessageLogger.h b/src/qtviewer/MessageLogger.h new file mode 100644 index 0000000000..9b4b7df446 --- /dev/null +++ b/src/qtviewer/MessageLogger.h @@ -0,0 +1,40 @@ +#include +#include + +// SINGLETON +class MessageLogger : public QObject +{ + Q_OBJECT + +signals: + void logMessage(const QString& message); + +public: + static MessageLogger& getInstance() + { + static MessageLogger instance; + return instance; + } + + // Convenience function to log a message + static void log(const std::string& message) + { + getInstance().emitMessage(message); + } + +private slots: + void emitMessage(const std::string& message) + { + emit logMessage(QString::fromStdString(message)); + } + +private: + MessageLogger() {} + ~MessageLogger() {} + + // Disable copy constructor for MessageLogger + MessageLogger(const MessageLogger&) = delete; + + // Disable copy assignment operator for MessageLogger + MessageLogger& operator=(const MessageLogger&) = delete; +}; diff --git a/src/qtviewer/MouseHandler.cpp b/src/qtviewer/MouseHandler.cpp new file mode 100644 index 0000000000..2188f141a4 --- /dev/null +++ b/src/qtviewer/MouseHandler.cpp @@ -0,0 +1,45 @@ +#include "MouseHandler.h" +#include +#include "MessageLogger.h" + +MouseHandler::MouseHandler(osgViewer::Viewer* viewer) : + osgGA::TrackballManipulator(), + viewer(viewer) +{ } + +bool MouseHandler::handle ( + const osgGA::GUIEventAdapter& ea, + const osgGA::GUIActionAdapter& aa) +{ + MessageLogger::log("test from MouseHandler.h"); + switch (ea.getEventType()) { + case (osgGA::GUIEventAdapter::DRAG): + { + if (ea.getButton() == osgGA::GUIEventAdapter::RIGHT_MOUSE_BUTTON) + { // pan view + // Calculate the delta movement + float deltaX = ea.getX() - _ga_t0->getXnormalized(); + float deltaY = ea.getY() - _ga_t1->getYnormalized(); + + // Implement panning logic + osg::Vec3d eye, center, up; + getInverseMatrix().getLookAt(eye, center, up); + osg::Vec3d right = (eye - center) ^ up; + osg::Matrixd rotationMatrix = osg::Matrix::rotate(-deltaX * 0.1, up) * osg::Matrix::rotate(deltaY * 0.1, right); + + osg::Matrixd newMatrix = getMatrix() * rotationMatrix; + setByMatrix(newMatrix); + + return true; // Event handled + } + if (ea.getButton() == osgGA::GUIEventAdapter::LEFT_MOUSE_BUTTON) + { + return false; + } + // If not a right mouse button drag, let the base class handle it + //return osgGA::TrackballManipulator::handle(ea, aa); + } + default: + return false; + } +} diff --git a/src/qtviewer/MouseHandler.h b/src/qtviewer/MouseHandler.h new file mode 100644 index 0000000000..386e2deb9f --- /dev/null +++ b/src/qtviewer/MouseHandler.h @@ -0,0 +1,16 @@ +#include +#include + +class MouseHandler : public osgGA::TrackballManipulator +{ + public: + MouseHandler(osgViewer::Viewer* viewer); + + using osgGA::GUIEventHandler::handle; + virtual bool handle( + const osgGA::GUIEventAdapter& ea, + const osgGA::GUIActionAdapter& aa + ); + private: + osgViewer::Viewer* viewer; +}; diff --git a/src/qtviewer/ParseIfcFile.cpp b/src/qtviewer/ParseIfcFile.cpp index e5dc8e99bf..115cb10c0c 100644 --- a/src/qtviewer/ParseIfcFile.cpp +++ b/src/qtviewer/ParseIfcFile.cpp @@ -1,4 +1,5 @@ #include "ParseIfcFile.h" +#include "MessageLogger.h" #include "../ifcgeom_schema_agnostic/IfcGeomIterator.h" @@ -6,11 +7,6 @@ ParseIfcFile::ParseIfcFile() {} ParseIfcFile::~ParseIfcFile() {} -void ParseIfcFile::outputMsg(const std::string& msg) -{ - emit parsingInfo(QString::fromStdString(msg)); -} - void ParseIfcFile::Parse(const std::string& filePath) { IfcParse::IfcFile file(filePath); @@ -21,7 +17,7 @@ void ParseIfcFile::Parse(const std::string& filePath) IfcGeom::Iterator* it = new IfcGeom::Iterator(settings, &file); if (!it->initialize()) { - outputMsg("Error: Iterator failed to initialize! Aborting."); + MessageLogger::log("Error: Iterator failed to initialize! Aborting."); delete it; return; } @@ -29,14 +25,14 @@ void ParseIfcFile::Parse(const std::string& filePath) do { //const IfcGeom::BRepElement* bRepElem = it->get_native(); const IfcGeom::TriangulationElement* triElem = static_cast(it->get()); - outputMsg(triElem->type() + ": " + triElem->name()); + MessageLogger::log(triElem->type() + ": " + triElem->name()); const boost::shared_ptr& triElemGeom = triElem->geometry_pointer(); // materials const std::vector& elemMats = triElemGeom->materials(); for (auto mat : elemMats) { - outputMsg(" " + mat.original_name()); + MessageLogger::log(" " + mat.original_name()); } } while (it->next()); -} \ No newline at end of file +} diff --git a/src/qtviewer/ParseIfcFile.h b/src/qtviewer/ParseIfcFile.h index 2d1a7c5eb0..36bdda4655 100644 --- a/src/qtviewer/ParseIfcFile.h +++ b/src/qtviewer/ParseIfcFile.h @@ -7,14 +7,6 @@ class ParseIfcFile : public QObject { - Q_OBJECT - -signals: - void parsingInfo(const QString& info); - -private: - void outputMsg(const std::string& msg); - public: ParseIfcFile(); ~ParseIfcFile(); @@ -22,4 +14,4 @@ public: void Parse(const std::string& filePath); }; -#endif // PARSEIFCFILE_H \ No newline at end of file +#endif // PARSEIFCFILE_H diff --git a/src/qtviewer/main.cpp b/src/qtviewer/main.cpp index 2971093c22..3652d247a5 100644 --- a/src/qtviewer/main.cpp +++ b/src/qtviewer/main.cpp @@ -1,10 +1,11 @@ -#include "MainWindow.h" +#include "MainWindow.h" #include int main(int argc, char *argv[]) { QApplication app(argc, argv); + qreal dpiScale = app.devicePixelRatio(); // Set the icon in the window title-bar on mswindows // and dock icon on macos. @@ -15,7 +16,7 @@ int main(int argc, char *argv[]) // mswindows - .rc file // macos - .icns file (using CMake) - MainWindow window; + MainWindow window(dpiScale); window.show();