From 7f8ef38823af46984c42848a55891b7fc2ce9777 Mon Sep 17 00:00:00 2001 From: knoppix Date: Thu, 29 May 2025 20:39:54 -0500 Subject: [PATCH] Add possibility to see the interior of the model using cutting plane - New menu entry: Enable cutting plane - New menu entry: Wheel mode. The wheel can act as zoom control or for moving the cutting plane. Alternatively one can press Ctrl modifier to make a Wheel controlling a cutting plane. - Pressing Ctrl modifier and a left mouse button rotates a cutting plane. - Minimal GLSL version for mesh.vert shader is bumped to 140, as glClipPlane is deprecated and the similar shader functionality was added in GLSL version 1.40 --- gl/cutting_quad.frag | 24 ++++++ gl/gl.qrc | 1 + gl/mesh.frag | 3 +- gl/mesh.vert | 14 +++- src/canvas.cpp | 180 ++++++++++++++++++++++++++++++++++++------- src/canvas.h | 16 ++++ src/window.cpp | 55 +++++++++++++ src/window.h | 7 ++ 8 files changed, 269 insertions(+), 31 deletions(-) create mode 100644 gl/cutting_quad.frag diff --git a/gl/cutting_quad.frag b/gl/cutting_quad.frag new file mode 100644 index 00000000..eb2f954c --- /dev/null +++ b/gl/cutting_quad.frag @@ -0,0 +1,24 @@ +#version 120 + +uniform float zoom; + +varying vec3 ec_pos; + + +void main() { + //Color of the cutting plane + vec3 cbase3 = vec3(1.0, 0.0, 0.0); + vec3 cbase2 = vec3(1.0, 0.0, 0.0); + vec3 cbase00 = vec3(0.5, 0.0, 0.0); + + vec3 ec_normal = normalize(cross(dFdx(ec_pos), dFdy(ec_pos))); + ec_normal.z *= zoom; + ec_normal = normalize(ec_normal); + + float a = dot(ec_normal, vec3(0.0, 0.0, 1.0)); + float b = dot(ec_normal, vec3(-0.57, -0.57, 0.57)); + + + gl_FragColor = vec4((a*cbase2 + (1-a)*cbase00)*0.5 + + (b*cbase3 + (1-b)*cbase00)*0.5, 1.0); +} diff --git a/gl/gl.qrc b/gl/gl.qrc index a8065307..5fe77e36 100644 --- a/gl/gl.qrc +++ b/gl/gl.qrc @@ -7,6 +7,7 @@ mesh_light.frag quad.frag quad.vert + cutting_quad.frag colored_lines.frag colored_lines.vert sphere.stl diff --git a/gl/mesh.frag b/gl/mesh.frag index d7a54d55..d8249187 100644 --- a/gl/mesh.frag +++ b/gl/mesh.frag @@ -4,6 +4,7 @@ uniform float zoom; varying vec3 ec_pos; + void main() { vec3 base3 = vec3(0.99, 0.96, 0.89); vec3 base2 = vec3(0.92, 0.91, 0.83); @@ -17,5 +18,5 @@ void main() { float b = dot(ec_normal, vec3(-0.57, -0.57, 0.57)); gl_FragColor = vec4((a*base2 + (1-a)*base00)*0.5 + - (b*base3 + (1-b)*base00)*0.5, 1.0); + (b*base3 + (1-b)*base00)*0.5, 1.0); } diff --git a/gl/mesh.vert b/gl/mesh.vert index e60e76bb..d3987de4 100644 --- a/gl/mesh.vert +++ b/gl/mesh.vert @@ -1,13 +1,21 @@ -#version 120 +#version 140 attribute vec3 vertex_position; uniform mat4 transform_matrix; uniform mat4 view_matrix; +uniform mat4 cutting_plane_matrix; varying vec3 ec_pos; void main() { - gl_Position = view_matrix*transform_matrix* - vec4(vertex_position, 1.0); + vec4 origpos = transform_matrix*vec4(vertex_position, 1.0); + gl_Position = view_matrix*origpos; ec_pos = gl_Position.xyz; + + vec4 vPos = view_matrix * transform_matrix* vec4(vertex_position, 1.0); + + vec4 u_plane0 = transpose(inverse(cutting_plane_matrix))*vec4(0,0,1,0); + gl_ClipDistance[0] = dot(u_plane0, origpos); //If you want to clip using the camera coordinate system + //gl_ClipDistance[0] = dot(u_plane0, in_Vertex); //If you want to clip using the local coordinate system + //gl_ClipDistance[0] = dot(u_plane0, camMat * modelViewM * in_Vertex); //If you want it in global system } diff --git a/src/canvas.cpp b/src/canvas.cpp index ad4ec37b..987fd5df 100644 --- a/src/canvas.cpp +++ b/src/canvas.cpp @@ -225,6 +225,16 @@ void Canvas::set_drawMode(enum DrawMode mode) update(); } +void Canvas::set_enableCut(bool d) { + enableCut = d; + update(); +} + +void Canvas::set_wheelMode(enum WheelMode mode) { + wheelMode = mode; + update(); +} + void Canvas::clear_status() { status = ""; @@ -235,8 +245,16 @@ void Canvas::initializeGL() { initializeOpenGLFunctions(); + cutting_quad_fragshader = new QOpenGLShader(QOpenGLShader::Fragment); + cutting_quad_fragshader->compileSourceFile(":/gl/cutting_quad.frag"); + mesh_vertshader = new QOpenGLShader(QOpenGLShader::Vertex); mesh_vertshader->compileSourceFile(":/gl/mesh.vert"); + + cutting_quad_shaderprog.addShader(mesh_vertshader); + cutting_quad_shaderprog.addShader(cutting_quad_fragshader); + cutting_quad_shaderprog.link(); + mesh_shader.addShader(mesh_vertshader); mesh_shader.addShaderFromSourceFile(QOpenGLShader::Fragment, ":/gl/mesh.frag"); mesh_shader.link(); @@ -249,7 +267,7 @@ void Canvas::initializeGL() mesh_meshlight_shader.addShader(mesh_vertshader); mesh_meshlight_shader.addShaderFromSourceFile(QOpenGLShader::Fragment, ":/gl/mesh_light.frag"); mesh_meshlight_shader.link(); - + backdrop = new Backdrop(); axis = new Axis(); } @@ -336,8 +354,88 @@ void Canvas::draw_mesh() const GLuint vp = selected_mesh_shader->attributeLocation("vertex_position"); glEnableVertexAttribArray(vp); - // Then draw the mesh with that vertex position - mesh->draw(vp); + if (enableCut) { + glUniformMatrix4fv( + selected_mesh_shader->uniformLocation("cutting_plane_matrix"), + 1, GL_FALSE, cutting_plane_matrix().data()); + // Calculate clipping plane + glEnable(GL_CLIP_DISTANCE0); + glEnable(GL_STENCIL_TEST); + glEnable(GL_CULL_FACE); + glClear(GL_STENCIL_BUFFER_BIT); + glDisable(GL_DEPTH_TEST); + glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE); + + glStencilFunc(GL_ALWAYS, 0, 0); + glStencilOp(GL_KEEP, GL_KEEP, GL_INCR); + glCullFace(GL_FRONT); // render back faces + mesh -> draw(vp); + + glStencilOp(GL_KEEP, GL_KEEP, GL_DECR); + glCullFace(GL_BACK); // render front faces + mesh -> draw(vp); + + glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); + glDisable(GL_CULL_FACE); + glEnable(GL_DEPTH_TEST); + glDisable(GL_CLIP_DISTANCE0); + glStencilFunc(GL_NOTEQUAL, 0, ~0); + + // Rendering the mesh + glDisable(GL_STENCIL_TEST); + glEnable(GL_CLIP_DISTANCE0); + glUniformMatrix4fv( + selected_mesh_shader->uniformLocation("transform_matrix"), + 1, GL_FALSE, transform_matrix().data()); + + mesh->draw(vp); + + // reload shader program to draw the colored clipping plane + selected_mesh_shader->release(); + selected_mesh_shader = &cutting_quad_shaderprog; + selected_mesh_shader->bind(); + + glUniform1f(selected_mesh_shader->uniformLocation("zoom"), 1/zoom); + glUniformMatrix4fv( + selected_mesh_shader->uniformLocation("transform_matrix"), + 1, GL_FALSE, cutting_plane_matrix().data()); + glUniformMatrix4fv( + selected_mesh_shader->uniformLocation("view_matrix"), + 1, GL_FALSE, view_matrix().data()); + + glEnable(GL_STENCIL_TEST); + glDisable(GL_CLIP_DISTANCE0); + + // Define a BIG quad to render. It should be big to assure covering of clip area + unsigned int VBO; + float vertices[] = { + -1000.0f, 1000.0f, 0.0f, + -1000.0f, -1000.0f, 0.0f, + 1000.0f, -1000.0f, 0.0f, + 1000.0f, 1000.0f, 0.0f}; + + glBindBuffer(GL_ARRAY_BUFFER, VBO); + glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW); + glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, (void*)0); + glEnableVertexAttribArray(0); + + // render quad + glDrawArrays(GL_QUADS, 0, 4); + + // release buffers + glBindBuffer(GL_ARRAY_BUFFER, 0); + glDeleteBuffers(1, &VBO); + + glDisable(GL_STENCIL_TEST); + glEnable(GL_CLIP_DISTANCE0); + } + else + { + glDisable(GL_CLIP_DISTANCE0); + + // Then draw the mesh with that vertex position + mesh->draw(vp); + } // Reset draw mode for the background and anything else that needs to be drawn glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); @@ -346,11 +444,13 @@ void Canvas::draw_mesh() glDisableVertexAttribArray(vp); selected_mesh_shader->release(); } + QMatrix4x4 Canvas::orient_matrix() const { QMatrix4x4 m = currentTransform; return m; } + QMatrix4x4 Canvas::transform_matrix() const { QMatrix4x4 m = orient_matrix(); @@ -379,6 +479,20 @@ QMatrix4x4 Canvas::view_matrix() const return m; } +QMatrix4x4 Canvas::cutting_plane_matrix() const +{ + QMatrix4x4 m = QMatrix4x4( + 1.0f, 0.0f, 0.0f, 0.0f, + 0.0f, 1.0f, 0.0f, 0.0f, + 0.0f, 0.0f, 1.0f, 0.0f, + 0.0f, 0.0f, 0.0f, 1.0f); + + m.rotate(cp_tilt, QVector3D(1, 0, 0)); + m.rotate(cp_yaw, QVector3D(0, 1, 0)); + m.translate(0, 0, cp_shift); + return m; +} + void Canvas::mousePressEvent(QMouseEvent* event) { if (event->button() == Qt::LeftButton || @@ -458,20 +572,27 @@ void Canvas::mouseMoveEvent(QMouseEvent* event) auto d = p - mouse_pos; - if (event->buttons() & Qt::LeftButton) + if (event->modifiers() & Qt::ControlModifier) + { + if (event->buttons() & Qt::LeftButton) + { + cp_yaw = fmod(cp_yaw - d.x(), 360); + cp_tilt = fmod(cp_tilt - d.y(), 360); + update(); + } + } else if (event->buttons() & Qt::LeftButton) { QPointF p1r = changeMouseCoordinates(mouse_pos); QPointF p2r = changeMouseCoordinates(p); calcArcballTransform(p1r,p2r); update(); - } - else if (event->buttons() & Qt::RightButton) + } else if (event->buttons() & Qt::RightButton) { center = transform_matrix().inverted() * - view_matrix().inverted() * - QVector3D(-d.x() / (0.5*width()), - d.y() / (0.5*height()), 0); + view_matrix().inverted() * + QVector3D(-d.x() / (0.5*width()), + d.y() / (0.5*height()), 0); update(); } mouse_pos = p; @@ -487,27 +608,32 @@ void Canvas::wheelEvent(QWheelEvent *event) QVector3D a = transform_matrix().inverted() * view_matrix().inverted() * v; - if (event->angleDelta().y() < 0) - { - for (int i=0; i > event->angleDelta().y(); --i) - if (invertZoom) - zoom /= 1.001; - else - zoom *= 1.001; - } - else if (event->angleDelta().y() > 0) + if (event->modifiers() & Qt::ControlModifier || wheelMode == wheelcut) + cp_shift += (event->delta())/10000.0f; + else { - for (int i=0; i < event->angleDelta().y(); ++i) - if (invertZoom) - zoom *= 1.001; - else - zoom /= 1.001; + if (event->angleDelta().y() < 0) + { + for (int i=0; i > event->angleDelta().y(); --i) + if (invertZoom) + zoom /= 1.001; + else + zoom *= 1.001; + } + else if (event->angleDelta().y() > 0) + { + for (int i=0; i < event->angleDelta().y(); ++i) + if (invertZoom) + zoom *= 1.001; + else + zoom /= 1.001; + } + // Then find the cursor's GL position post-zoom and adjust center. + QVector3D b = transform_matrix().inverted() * + view_matrix().inverted() * v; + center += b - a; } - // Then find the cursor's GL position post-zoom and adjust center. - QVector3D b = transform_matrix().inverted() * - view_matrix().inverted() * v; - center += b - a; update(); } diff --git a/src/canvas.h b/src/canvas.h index 8dab30c4..33c5b7f8 100644 --- a/src/canvas.h +++ b/src/canvas.h @@ -12,6 +12,7 @@ class Axis; enum ViewPoint {centerview, isoview, topview, bottomview, leftview, rightview, frontview, backview}; enum DrawMode {shaded, wireframe, surfaceangle, meshlight, DRAWMODECOUNT}; +enum WheelMode {wheelzoom, wheelcut}; class Canvas : public QOpenGLWidget, protected QOpenGLFunctions { @@ -28,6 +29,8 @@ class Canvas : public QOpenGLWidget, protected QOpenGLFunctions void draw_axes(bool d); void invert_zoom(bool d); void set_drawMode(enum DrawMode mode); + void set_enableCut(bool d); + void set_wheelMode(enum WheelMode mode); void common_view_change(enum ViewPoint c); void setResetTransformOnLoad(bool d); @@ -73,10 +76,15 @@ public slots: QMatrix4x4 transform_matrix() const; QMatrix4x4 aspect_matrix() const; QMatrix4x4 view_matrix() const; + QMatrix4x4 cutting_plane_matrix() const; + void resetTransform(); QPointF changeMouseCoordinates(QPoint p); void calcArcballTransform(QPointF p1, QPointF p2); + QOpenGLShader* cutting_quad_fragshader; + QOpenGLShaderProgram cutting_quad_shaderprog; + QOpenGLShader* mesh_vertshader; QOpenGLShaderProgram mesh_shader; QOpenGLShaderProgram mesh_wireframe_shader; @@ -112,9 +120,17 @@ public slots: float zoom; QMatrix4x4 currentTransform; + + float cp_tilt; + float cp_yaw; + float cp_shift; + float perspective; enum DrawMode drawMode; + enum WheelMode wheelMode; + bool drawAxes; + bool enableCut; bool invertZoom; bool resetTransformOnLoad; Q_PROPERTY(float perspective MEMBER perspective WRITE set_perspective); diff --git a/src/window.cpp b/src/window.cpp index c8aaba76..ee29b0b8 100644 --- a/src/window.cpp +++ b/src/window.cpp @@ -11,7 +11,9 @@ const QString Window::INVERT_ZOOM_KEY = "invertZoom"; const QString Window::AUTORELOAD_KEY = "autoreload"; const QString Window::DRAW_AXES_KEY = "drawAxes"; const QString Window::PROJECTION_KEY = "projection"; +const QString Window::WHEELMODE_KEY = "wheelMode"; const QString Window::DRAW_MODE_KEY = "drawMode"; +const QString Window::CUTTING_PLANE_KEY = "cuttingplane"; const QString Window::WINDOW_GEOM_KEY = "windowGeometry"; const QString Window::RESET_TRANSFORM_ON_LOAD_KEY = "resetTransformOnLoad"; @@ -35,6 +37,9 @@ Window::Window(QWidget *parent) : wireframe_action(new QAction("Wireframe", this)), surfaceangle_action(new QAction("Surface Angle", this)), meshlight_action(new QAction("Shaded ambient and directive light source", this)), + cuttingplane_action(new QAction("Enable cutting plane", this)), + wheelmode_cut_action(new QAction("Wheel controls cut", this)), + wheelmode_zoom_action(new QAction("Wheel controls zoom", this)), drawModePrefs_action(new QAction("Draw Mode Settings")), axes_action(new QAction("Draw Axes", this)), invert_zoom_action(new QAction("Invert Zoom", this)), @@ -135,6 +140,19 @@ Window::Window(QWidget *parent) : QObject::connect(projections, &QActionGroup::triggered, this, &Window::on_projection); + const auto wheel_menu = view_menu->addMenu("Wheel Mode"); + wheel_menu->addAction(wheelmode_cut_action); + wheel_menu->addAction(wheelmode_zoom_action); + const auto wheelmodes = new QActionGroup(wheel_menu); + for (auto p : {wheelmode_cut_action, wheelmode_zoom_action}) + { + wheelmodes->addAction(p); + p->setCheckable(true); + } + wheelmodes->setExclusive(true); + QObject::connect(wheelmodes, &QActionGroup::triggered, + this, &Window::on_wheelmode); + const auto draw_menu = view_menu->addMenu("Draw Mode"); draw_menu->addAction(shaded_action); draw_menu->addAction(wireframe_action); @@ -186,6 +204,11 @@ Window::Window(QWidget *parent) : QObject::connect(axes_action, &QAction::triggered, this, &Window::on_drawAxes); + view_menu->addAction(cuttingplane_action); + cuttingplane_action->setCheckable(true); + QObject::connect(cuttingplane_action, &QAction::triggered, + this, &Window::on_cuttingplane); + view_menu->addAction(invert_zoom_action); invert_zoom_action->setCheckable(true); QObject::connect(invert_zoom_action, &QAction::triggered, @@ -233,6 +256,10 @@ void Window::load_persist_settings(){ canvas->draw_axes(draw_axes); axes_action->setChecked(draw_axes); + bool cuttingplane_b = settings.value(CUTTING_PLANE_KEY, false).toBool(); + cuttingplane_action->setChecked(cuttingplane_b); + canvas->set_enableCut(cuttingplane_b); + QString projection = settings.value(PROJECTION_KEY, "perspective").toString(); if(projection == "perspective"){ canvas->view_perspective(Canvas::P_PERSPECTIVE, false); @@ -242,6 +269,12 @@ void Window::load_persist_settings(){ orthographic_action->setChecked(true); } + QString wheelmode = settings.value(WHEELMODE_KEY, "wheelMode").toString(); + if(wheelmode == "wheelzoom") + wheelmode_zoom_action->trigger(); + else + wheelmode_cut_action->trigger(); + QString path = settings.value(OPEN_EXTERNAL_KEY, "").toString(); if (!QDir::isAbsolutePath(path) && !path.isEmpty()) { @@ -389,6 +422,21 @@ void Window::on_projection(QAction* proj) } } +void Window::on_wheelmode(QAction* mode) +{ + if (mode == wheelmode_cut_action) + { + canvas->set_wheelMode(wheelcut); + QSettings().setValue(WHEELMODE_KEY, "wheelcut"); + } + else + { + canvas->set_wheelMode(wheelzoom); + QSettings().setValue(WHEELMODE_KEY, "wheelzoom"); + } +} + + void Window::on_drawMode(QAction* act) { // On mode change hide prefs first @@ -415,10 +463,17 @@ void Window::on_drawMode(QAction* act) drawModePrefs_action->setEnabled(true); mode = meshlight; } + canvas->set_drawMode(mode); QSettings().setValue(DRAW_MODE_KEY, mode); } +void Window::on_cuttingplane(bool d) +{ + canvas->set_enableCut(d); + QSettings().setValue(CUTTING_PLANE_KEY, d); +} + void Window::on_drawAxes(bool d) { canvas->draw_axes(d); diff --git a/src/window.h b/src/window.h index f13137b8..ac8ec77e 100644 --- a/src/window.h +++ b/src/window.h @@ -40,7 +40,9 @@ public slots: private slots: void on_projection(QAction* proj); + void on_wheelmode(QAction* mode); void on_drawMode(QAction* mode); + void on_cuttingplane(bool d); void on_drawAxes(bool d); void on_invertZoom(bool d); void on_resetTransformOnLoad(bool d); @@ -81,6 +83,9 @@ private slots: QAction* const wireframe_action; QAction* const surfaceangle_action; QAction* const meshlight_action; + QAction* const cuttingplane_action; + QAction* const wheelmode_cut_action; + QAction* const wheelmode_zoom_action; QAction* const drawModePrefs_action; QAction* const axes_action; QAction* const invert_zoom_action; @@ -101,7 +106,9 @@ private slots: const static QString AUTORELOAD_KEY; const static QString DRAW_AXES_KEY; const static QString PROJECTION_KEY; + const static QString WHEELMODE_KEY; const static QString DRAW_MODE_KEY; + const static QString CUTTING_PLANE_KEY; const static QString WINDOW_GEOM_KEY; const static QString RESET_TRANSFORM_ON_LOAD_KEY;