#include "glview.h" #include <QMouseEvent> #include <qdebug.h>
GLView::GLView(QWidget* parent) : QOpenGLWidget{ parent }, m_workPlane(nullptr), m_model(nullptr), m_viewCube(nullptr) , b_drawLineState(false), b_selectLineState(false), m_enableDrawPlane(true) { m_action_deleteSelecedLine = new QAction("delete"); m_onSelectMenu = new QMenu(this); m_onSelectMenu->setStyleSheet(R"( QMenu { background-color: rgba(240, 240, 240, 0.9); border: 0.5px solid white; border-radius: 4px; padding: 4px 0; }
QMenu::item { padding: 6px 20px; color: #333333; background-color: transparent; }
QMenu::item:selected { background-color: rgba(220, 220, 220, 0.8); color: #000000; }
QMenu::item:disabled { color: #aaaaaa; } )"); m_onSelectMenu->addAction(m_action_deleteSelecedLine);
connect(m_action_deleteSelecedLine, &QAction::triggered, this, [=]() { m_lineDrawer.deleteSelectedLine(); update(); m_onSelectMenu->hide(); }); }
GLView::~GLView() { makeCurrent(); delete m_model; delete m_workPlane; delete m_viewCube; doneCurrent(); }
void GLView::beginDrawLines() { b_drawLineState = true; m_lineDrawer.setCurrentLineColor(QVector4D(0.0f, 0.0f, 1.0f, 1.0f)); m_lineDrawer.setSavedLineColor(QVector4D(1.0f, 1.0f, 0.0f, 1.0f)); }
void GLView::clearAllLines() { m_lineDrawer.clearAllLines(); update(); }
void GLView::undoLastLine() { m_lineDrawer.undoLastLine(); update(); }
void GLView::selectLine() { b_selectLineState = true; }
QVector3D GLView::screenToWorld_3D_PointSnap(const QPoint& pos) { int x = pos.x(); int y = height() - pos.y();
QVector3D camPos = m_viewMat.inverted().column(3).toVector3D(); QMatrix4x4 invMVP = (m_projectionMat * m_viewMat).inverted();
float ndcX = (2.0f * x) / width() - 1.0f; float ndcY = (2.0f * y) / height() - 1.0f;
QVector4D nearNDC(ndcX, ndcY, -1.0f, 1.0f); QVector4D farNDC(ndcX, ndcY, 1.0f, 1.0f); QVector3D nearWorld = (invMVP * nearNDC).toVector3D() / (invMVP * nearNDC).w(); QVector3D farWorld = (invMVP * farNDC).toVector3D() / (invMVP * farNDC).w(); QVector3D rayDir = (farWorld - nearWorld).normalized();
const float endpointSnapThreshold = 0.1f; auto endpoints = m_lineDrawer.getAllEndpoints(); QVector3D closestEndpoint; float minEndpointDistance = FLT_MAX;
for (const auto& endpoint : endpoints) { QVector3D camToEndpoint = endpoint - camPos; float t = QVector3D::dotProduct(camToEndpoint, rayDir); QVector3D projection = camPos + rayDir * t; float distance = (endpoint - projection).length();
if (t > 0 && distance < minEndpointDistance && distance < endpointSnapThreshold) { minEndpointDistance = distance; closestEndpoint = endpoint; } } if (minEndpointDistance != FLT_MAX) { return closestEndpoint; }
const float gridSnapThreshold = 0.1f; QVector3D planeIntersection;
if (m_workPlane->getRayPlaneIntersection(camPos, rayDir, m_workPlane->para(), planeIntersection)) { QVector3D closestGridPoint; float minGridDistance = FLT_MAX;
for (const QVector3D& gridPoint : m_workPlane->gridPoints()) { float distance = (planeIntersection - gridPoint).length(); if (distance < minGridDistance && distance < gridSnapThreshold) { minGridDistance = distance; closestGridPoint = gridPoint; } }
if (minGridDistance != FLT_MAX) { return closestGridPoint; } }
if (m_workPlane->getRayPlaneIntersection(camPos, rayDir, m_workPlane->para(), planeIntersection)) { return planeIntersection; }
float defaultDistance = 5.0f; return camPos + rayDir * defaultDistance; }
QVector3D GLView::screenToWorldRawRay(const QPoint& pos) { int x = pos.x(); int y = height() - pos.y();
QVector3D camPos = m_viewMat.inverted().column(3).toVector3D(); QMatrix4x4 invMVP = (m_projectionMat * m_viewMat).inverted();
float ndcX = (2.0f * x) / width() - 1.0f; float ndcY = (2.0f * y) / height() - 1.0f;
QVector4D farNDC(ndcX, ndcY, 1.0f, 1.0f); QVector3D farWorld = (invMVP * farNDC).toVector3D() / (invMVP * farNDC).w();
const float rayLength = 100.0f; QVector3D rayDir = (farWorld - camPos).normalized(); return camPos + rayDir * rayLength; }
float GLView::rayLineDistance( const QPoint& mousePos, const QVector3D& rayOrigin, const QVector3D& rayDir, const QVector3D& lineStart, const QVector3D& lineEnd ) { QVector3D lineVec = lineEnd - lineStart; QVector3D originToLineStart = rayOrigin - lineStart;
float a = QVector3D::dotProduct(rayDir, rayDir); float b = QVector3D::dotProduct(rayDir, lineVec); float c = QVector3D::dotProduct(lineVec, lineVec); float d = QVector3D::dotProduct(rayDir, originToLineStart); float e = QVector3D::dotProduct(lineVec, originToLineStart); float denominator = a * c - b * b;
float s, t; if (denominator < 1e-6) { s = 0.0f; t = d / a; } else { s = (b * e - c * d) / denominator; t = (a * e - b * d) / denominator; } s = qBound(0.0f, s, 1.0f); t = qMax(0.0f, t);
auto worldToScreen = [&](const QVector3D& worldPos) -> QPointF { QVector4D clipPos = m_projectionMat * m_viewMat * QVector4D(worldPos, 1.0f); QVector3D ndcPos = clipPos.toVector3D() / clipPos.w(); float x = (ndcPos.x() + 1.0f) * 0.5f * width(); float y = height() - (ndcPos.y() + 1.0f) * 0.5f * height(); return QPointF(x, y); };
QPointF lineStartScreen = worldToScreen(lineStart); QPointF lineEndScreen = worldToScreen(lineEnd); QPointF mouseScreen = QPointF(mousePos);
QPointF lineVecScreen = lineEndScreen - lineStartScreen; QPointF mouseToStartScreen = mouseScreen - lineStartScreen; float dotProduct = mouseToStartScreen.x() * lineVecScreen.x() + mouseToStartScreen.y() * lineVecScreen.y(); float len2 = lineVecScreen.x() * lineVecScreen.x() + lineVecScreen.y() * lineVecScreen.y(); float screenDistance;
if (len2 < 1e-6) { screenDistance = hypot(mouseToStartScreen.x(), mouseToStartScreen.y()); } else if (dotProduct <= 0.0f) { screenDistance = hypot(mouseToStartScreen.x(), mouseToStartScreen.y()); } else if (dotProduct >= len2) { QPointF mouseToEndScreen = mouseScreen - lineEndScreen; screenDistance = hypot(mouseToEndScreen.x(), mouseToEndScreen.y()); } else { float tScreen = dotProduct / len2; QPointF closestOnLineScreen = lineStartScreen + tScreen * lineVecScreen; QPointF delta = mouseScreen - closestOnLineScreen; screenDistance = hypot(delta.x(), delta.y()); }
return screenDistance; }
bool GLView::checkLineHit(const QPoint& mousePos) { QVector3D rayOrigin = m_viewMat.inverted().column(3).toVector3D(); QVector3D rayEnd = screenToWorldRawRay(mousePos); QVector3D rayDir = (rayEnd - rayOrigin).normalized();
const float hitThreshold = 1.8f; float minDist = FLT_MAX; int hitIndex = -1; auto& lines = m_lineDrawer.getAllLines();
for (size_t i = 0; i < lines.size(); ++i) { const auto& line = lines[i]; QVector3D lineStart = line.first; QVector3D lineEnd = line.second;
float distToStart = rayLineDistance(mousePos, rayOrigin, rayDir, lineStart, lineStart); float distToEnd = rayLineDistance(mousePos, rayOrigin, rayDir, lineEnd, lineEnd); const float endpointSnapThreshold = 5.0f; if (distToStart < endpointSnapThreshold || distToEnd < endpointSnapThreshold) { continue; }
float dist = rayLineDistance(mousePos, rayOrigin, rayDir, lineStart, lineEnd); if (dist < minDist && dist < hitThreshold) { minDist = dist; hitIndex = static_cast<int>(i); } }
m_lineDrawer.setSelectedIndex(hitIndex); return hitIndex != -1; }
bool GLView::getRayPlaneIntersection( const QVector3D& rayOrigin, const QVector3D& rayDir, const QVector3D& planeOrigin, const QVector3D& planeNormal, QVector3D& outIntersection ) { float denominator = QVector3D::dotProduct(rayDir, planeNormal); if (qAbs(denominator) < 1e-6) return false;
float t = QVector3D::dotProduct(planeOrigin - rayOrigin, planeNormal) / denominator; if (t < 0) return false;
outIntersection = rayOrigin + rayDir * t; return true; }
void GLView::initializeGL() { initializeOpenGLFunctions(); glEnable(GL_DEPTH_TEST); glEnable(GL_POLYGON_OFFSET_FILL);
initShader(m_lightShader, "./shader/modelLoading.vert", "./shader/modelLoading.frag"); initShader(m_viewCubeShader, "./shader/viewCubeTexture.vert", "./shader/viewCubeTexture.frag"); initShader(m_drawLineShader, "./shader/line.vert", "./shader/line.frag");
m_model = new Model(this); m_camera.lastFrame = QTime::currentTime().msecsSinceStartOfDay() / 1000.0;
initWorkPlane(); m_lineDrawer.init();
m_viewCube = new ViewCube(this, ":/MainWindow/resources/viewcube.png", width(), height()); }
void GLView::resizeGL(int w, int h) { glViewport(0, 0, w, h);
m_camera.SCR_WIDTH = w; m_camera.SCR_HEIGHT = h;
m_projectionMat.setToIdentity(); m_projectionMat.perspective(m_camera.Zoom, (float)w / h, 0.1f, 100.0f); }
void GLView::paintGL() { glClearColor(0.2f, 0.3f, 0.3f, 1.0f); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
float currentFrame = QTime::currentTime().msecsSinceStartOfDay() / 1000.0; m_camera.deltaTime = currentFrame - m_camera.lastFrame; m_camera.lastFrame = currentFrame;
m_lightShader.bind(); m_viewMat = b_drawLineState ? m_viewMat : m_camera.GetViewMatrix(); m_lightShader.setUniformValue("projection", m_projectionMat); m_lightShader.setUniformValue("view", m_viewMat); m_lightShader.setUniformValue("model", m_modelMat);
m_workPlane->drawPlane(); m_model->Draw(m_lightShader); m_lightShader.release();
m_drawLineShader.bind(); m_drawLineShader.setUniformValue("model", m_modelMat); m_drawLineShader.setUniformValue("view", m_viewMat); m_drawLineShader.setUniformValue("projection", m_projectionMat); m_lineDrawer.draw(m_drawLineShader); m_drawLineShader.release();
if (m_viewCube) { m_viewCubeShader.bind(); m_modelCube.setToIdentity(); m_modelCube.scale(0.5f);
m_viewMatNoTrans = b_drawLineState ? m_viewMatNoTrans : m_camera.GetViewMatrix4VieweCube(); m_projection4ViewCube.setToIdentity(); float aspect = (float)width() / height(); m_projection4ViewCube.perspective(45.0f, aspect, 0.1f, 100.0f);
QMatrix4x4 offViewCube; offViewCube.setToIdentity(); QVector2D projOffset(0.90f, 0.80f); offViewCube.translate(projOffset.x(), projOffset.y(), 0.0f); m_finalProjection = offViewCube * m_projection4ViewCube;
m_viewCubeShader.setUniformValue("model", m_modelCube); m_viewCubeShader.setUniformValue("view", m_viewMatNoTrans); m_viewCubeShader.setUniformValue("projection", m_finalProjection); m_viewCube->draw(m_viewCubeShader, m_modelCube, m_viewMatNoTrans, m_finalProjection); m_viewCubeShader.release(); } }
void GLView::initShader(QOpenGLShaderProgram& shader, const QString& vertexFile, const QString& fragFile) { bool result = shader.addShaderFromSourceFile(QOpenGLShader::Vertex, vertexFile); if (!result) qDebug() << shader.log();
result = shader.addShaderFromSourceFile(QOpenGLShader::Fragment, fragFile); if (!result) qDebug() << shader.log();
result = shader.link(); if (!result) qDebug() << shader.log(); }
bool GLView::event(QEvent* e) { makeCurrent();
if (e->type() == QEvent::MouseButtonPress) { auto mouseEvent = static_cast<QMouseEvent*>(e); if (mouseEvent->button() == Qt::LeftButton) { ViewCube::CubeFaceType face = m_viewCube->getFirstHitFace( mouseEvent->position().x(), mouseEvent->position().y(), width(), height(), m_viewMatNoTrans, m_finalProjection ); if (face != ViewCube::CubeFaceType::None) { m_camera.FitView(static_cast<int>(face)); } else if (b_drawLineState) { bool isHit = b_selectLineState ? checkLineHit(mouseEvent->pos()) : false; if (isHit) { update(); QPoint pos = mapFromGlobal(mouseEvent->pos()); m_onSelectMenu->move(pos); m_onSelectMenu->show(); } else { QVector3D startPos = screenToWorld_3D_PointSnap(mouseEvent->pos()); m_lineDrawer.startDraw(startPos);
if (m_enableDrawPlane && m_workPlane) { PlanePara workPara = m_workPlane->para(); QVector3D workNormal = workPara.normal.normalized(); QVector3D camFront = (m_viewMat.inverted().column(2).toVector3D()).normalized();
QVector3D projCamFront = camFront - QVector3D::dotProduct(camFront, workNormal) * workNormal; projCamFront.normalize(); m_drawPlaneNormal = projCamFront;
if (QVector3D::dotProduct(m_drawPlaneNormal, camFront) < 0) { m_drawPlaneNormal = -m_drawPlaneNormal; }
m_drawPlaneOrigin = startPos; } } } } else if (mouseEvent->button() == Qt::RightButton) { if (b_drawLineState) { m_lineDrawer.finishDraw(); b_drawLineState = false; } update(); } } else if (e->type() == QEvent::MouseButtonRelease) { auto mouseEvent = static_cast<QMouseEvent*>(e); if (mouseEvent->button() == Qt::LeftButton && b_drawLineState) { m_lineDrawer.saveDrawnLine(); update(); } } else if (e->type() == QEvent::MouseMove) { auto mouseEvent = static_cast<QMouseEvent*>(e); if (m_lineDrawer.isDrawing()) { QVector3D currentPos; QVector3D camPos = m_viewMat.inverted().column(3).toVector3D(); QVector3D rawEnd = screenToWorld_3D_PointSnap(mouseEvent->pos()); QVector3D rayDir = (rawEnd - camPos).normalized();
bool hasValidSnapPoint = false; auto endpoints = m_lineDrawer.getAllEndpoints(); const float endpointSnapThreshold = 0.1f; for (const auto& endpoint : endpoints) { if ((endpoint - rawEnd).length() < endpointSnapThreshold) { hasValidSnapPoint = true; break; } }
if (!hasValidSnapPoint) { const float gridSnapThreshold = 0.1f; for (const QVector3D& gridPoint : m_workPlane->gridPoints()) { if ((gridPoint - rawEnd).length() < gridSnapThreshold) { hasValidSnapPoint = true; break; } } }
if (m_enableDrawPlane && !hasValidSnapPoint) { QVector3D constrainedEnd; if (getRayPlaneIntersection(camPos, rayDir, m_drawPlaneOrigin, m_drawPlaneNormal, constrainedEnd)) { currentPos = constrainedEnd; } else { currentPos = rawEnd; } } else { currentPos = rawEnd; }
m_lineDrawer.updateDraw(currentPos); update(); } }
if (!b_drawLineState && m_camera.handle(e)) { update(); }
doneCurrent(); return QWidget::event(e); }
void GLView::initWorkPlane() { PlanePara para; para.origin = QVector3D(0.0f, -1.0f, 0.0f); para.normal = QVector3D(0.0f, 1.0f, 0.0f); para.halfLength = 5.0f; para.gridCntPerEdge = 15; para.offset = 0.0f;
m_workPlane = new WorkPlane(); m_workPlane->initPlane(para); }
|