#include "ViewCube.h" #include #include #include #include #include #include #include #include #include namespace { QRect getHomeButtonRect(int widgetWidth) { const int viewCubeSize = 150; // logical pixels const int buttonSize = 24; const int buttonOffsetX = 16; const int buttonOffsetY = 16; const int viewCubeX = widgetWidth - viewCubeSize; return QRect(viewCubeX + buttonOffsetX, buttonOffsetY, buttonSize, buttonSize); } } ViewCube::ViewCube() { m_homeButtonRenderer = new QSvgRenderer(QString(":/icons/home.svg")); for (int i = 0; i < 6; ++i) { m_faceTextures[i] = nullptr; } } ViewCube::~ViewCube() { for (int i = 0; i < 6; ++i) { delete m_faceTextures[i]; } delete m_textureShaderProgram; delete m_homeButtonRenderer; m_cubeVbo.destroy(); m_cubeVao.destroy(); m_axesVbo.destroy(); m_axesVao.destroy(); } void ViewCube::initializeGL() { initializeOpenGLFunctions(); initShaders(); createFaceTextures(); setupBuffers(); } void ViewCube::paintGL(QOpenGLShaderProgram* simpleShader, int simpleShaderColorLoc, const QMatrix4x4& viewMatrix, int width, int height, const QPoint& mousePos) { int viewCubeSize = 150 * QGuiApplication::primaryScreen()->devicePixelRatio(); glViewport(width - viewCubeSize, height - viewCubeSize, viewCubeSize, viewCubeSize); QRect viewCubeRect(width - viewCubeSize, 0, viewCubeSize, viewCubeSize); QPoint physicalMousePos = mousePos * QGuiApplication::primaryScreen()->devicePixelRatio(); m_isHovered = viewCubeRect.contains(physicalMousePos); float opacity = m_isHovered ? 1.0f : 0.5f; glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glClear(GL_DEPTH_BUFFER_BIT); QMatrix4x4 viewCubeProjection; viewCubeProjection.ortho(-2, 2, -2, 2, -10, 10); drawViewCube(viewCubeProjection, viewMatrix, opacity); drawAxes(simpleShader, simpleShaderColorLoc, viewCubeProjection, viewMatrix); QMatrix4x4 mvp = viewCubeProjection * viewMatrix; const float axisLen = 1.7f; QVector3D xAxisEnd(axisLen, 0.0f, 0.0f); QVector3D yAxisEnd(0.0f, 0.0f, axisLen); QVector3D zAxisEnd(0.0f, axisLen, 0.0f); QVector3D xAxisNdc = mvp.map(xAxisEnd); QVector3D yAxisNdc = mvp.map(yAxisEnd); QVector3D zAxisNdc = mvp.map(zAxisEnd); const int vpX = width - viewCubeSize; const int vpY = 0; const int vpW = viewCubeSize; const int vpH = viewCubeSize; const float dpr = QGuiApplication::primaryScreen()->devicePixelRatio(); m_xAxisLabelPos.setX( (xAxisNdc.x() + 1.0f) * vpW / 2.0f + vpX ); m_xAxisLabelPos.setY( (1.0f - xAxisNdc.y()) * vpH / 2.0f + vpY ); m_xAxisLabelPos /= dpr; m_yAxisLabelPos.setX( (yAxisNdc.x() + 1.0f) * vpW / 2.0f + vpX ); m_yAxisLabelPos.setY( (1.0f - yAxisNdc.y()) * vpH / 2.0f + vpY ); m_yAxisLabelPos /= dpr; m_zAxisLabelPos.setX( (zAxisNdc.x() + 1.0f) * vpW / 2.0f + vpX ); m_zAxisLabelPos.setY( (1.0f - zAxisNdc.y()) * vpH / 2.0f + vpY ); m_zAxisLabelPos /= dpr; glDisable(GL_BLEND); } void ViewCube::createFaceTextures() { QStringList labels = {"FRONT", "BACK", "TOP", "BOTTOM", "RIGHT", "LEFT"}; for (int i = 0; i < 6; ++i) { QImage image(128, 128, QImage::Format_RGBA8888); image.fill(qRgba(204, 204, 204, 255)); // light gray background QPainter painter(&image); painter.setPen(Qt::black); painter.setFont(QFont("Arial", 24, QFont::Bold)); painter.drawText(image.rect(), Qt::AlignCenter, labels[i]); m_faceTextures[i] = new QOpenGLTexture(image.mirrored(false, true)); m_faceTextures[i]->setMinificationFilter(QOpenGLTexture::Linear); m_faceTextures[i]->setMagnificationFilter(QOpenGLTexture::Linear); } } void ViewCube::initShaders() { m_textureShaderProgram = new QOpenGLShaderProgram(); if (!m_textureShaderProgram->addShaderFromSourceFile(QOpenGLShader::Vertex, ":/shaders/texture.vert")) { qCritical() << "ViewCube vertex shader compilation failed:" << m_textureShaderProgram->log(); return; } if (!m_textureShaderProgram->addShaderFromSourceFile(QOpenGLShader::Fragment, ":/shaders/texture.frag")) { qCritical() << "ViewCube fragment shader compilation failed:" << m_textureShaderProgram->log(); return; } if (!m_textureShaderProgram->link()) { qCritical() << "ViewCube shader program linking failed:" << m_textureShaderProgram->log(); return; } } void ViewCube::setupBuffers() { float size = 0.75; GLfloat vertices[] = { // positions // texture Coords // Front face -size, -size, size, 0.0f, 0.0f, size, -size, size, 1.0f, 0.0f, size, size, size, 1.0f, 1.0f, -size, size, size, 0.0f, 1.0f, // Back face -size, -size, -size, 1.0f, 0.0f, -size, size, -size, 1.0f, 1.0f, size, size, -size, 0.0f, 1.0f, size, -size, -size, 0.0f, 0.0f, // Top face -size, size, -size, 0.0f, 1.0f, -size, size, size, 0.0f, 0.0f, size, size, size, 1.0f, 0.0f, size, size, -size, 1.0f, 1.0f, // Bottom face -size, -size, -size, 1.0f, 1.0f, size, -size, -size, 0.0f, 1.0f, size, -size, size, 0.0f, 0.0f, -size, -size, size, 1.0f, 0.0f, // Right face size, -size, -size, 1.0f, 0.0f, size, size, -size, 1.0f, 1.0f, size, size, size, 0.0f, 1.0f, size, -size, size, 0.0f, 0.0f, // Left face -size, -size, -size, 0.0f, 0.0f, -size, -size, size, 1.0f, 0.0f, -size, size, size, 1.0f, 1.0f, -size, size, -size, 0.0f, 1.0f, }; m_cubeVao.create(); QOpenGLVertexArrayObject::Binder vaoBinder(&m_cubeVao); m_cubeVbo.create(); m_cubeVbo.bind(); m_cubeVbo.allocate(vertices, sizeof(vertices)); m_textureShaderProgram->enableAttributeArray(0); m_textureShaderProgram->setAttributeBuffer(0, GL_FLOAT, 0, 3, 5 * sizeof(GLfloat)); m_textureShaderProgram->enableAttributeArray(1); m_textureShaderProgram->setAttributeBuffer(1, GL_FLOAT, 3 * sizeof(GLfloat), 2, 5 * sizeof(GLfloat)); m_cubeVbo.release(); GLfloat axes_vertices[] = { 0.0f, 0.0f, 0.0f, 1.5f, 0.0f, 0.0f, // X 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.5f, // Y 0.0f, 0.0f, 0.0f, 0.0f, 1.5f, 0.0f // Z }; m_axesVao.create(); QOpenGLVertexArrayObject::Binder axesVaoBinder(&m_axesVao); m_axesVbo.create(); m_axesVbo.bind(); m_axesVbo.allocate(axes_vertices, sizeof(axes_vertices)); m_axesVbo.release(); } void ViewCube::drawViewCube(const QMatrix4x4& projection, const QMatrix4x4& view, float opacity) { if (!m_textureShaderProgram || !m_textureShaderProgram->isLinked()) return; glEnable(GL_CULL_FACE); m_textureShaderProgram->bind(); m_textureShaderProgram->setUniformValue("projectionMatrix", projection); m_textureShaderProgram->setUniformValue("modelViewMatrix", view); m_textureShaderProgram->setUniformValue("texture_diffuse1", 0); m_textureShaderProgram->setUniformValue("opacity", opacity); QOpenGLVertexArrayObject::Binder vaoBinder(&m_cubeVao); for (int i = 0; i < 6; ++i) { m_faceTextures[i]->bind(); glDrawArrays(GL_QUADS, i * 4, 4); } m_textureShaderProgram->release(); glDisable(GL_CULL_FACE); } void ViewCube::drawAxes(QOpenGLShaderProgram* simpleShader, int colorLoc, const QMatrix4x4& projection, const QMatrix4x4& view) { if (!simpleShader || !simpleShader->isLinked()) return; simpleShader->bind(); simpleShader->setUniformValue("projectionMatrix", projection); simpleShader->setUniformValue("modelViewMatrix", view); QOpenGLVertexArrayObject::Binder vaoBinder(&m_axesVao); m_axesVbo.bind(); simpleShader->enableAttributeArray(0); simpleShader->setAttributeBuffer(0, GL_FLOAT, 0, 3, 3 * sizeof(GLfloat)); m_axesVbo.release(); glLineWidth(2.0f); // X-axis (red) simpleShader->setUniformValue(colorLoc, QVector4D(1.0f, 0.0f, 0.0f, 1.0f)); glDrawArrays(GL_LINES, 0, 2); // Y-axis (green) simpleShader->setUniformValue(colorLoc, QVector4D(0.0f, 1.0f, 0.0f, 1.0f)); glDrawArrays(GL_LINES, 2, 2); // Z-axis (blue) simpleShader->setUniformValue(colorLoc, QVector4D(0.0f, 0.0f, 1.0f, 1.0f)); glDrawArrays(GL_LINES, 4, 2); glLineWidth(1.0f); } void ViewCube::paint2D(QPainter& painter, int widgetWidth, int widgetHeight) { QFont font("Arial", 10, QFont::Bold); painter.setFont(font); painter.setPen(Qt::white); QFontMetrics fm(font); QRect xRect = fm.boundingRect("X"); xRect.moveCenter(m_xAxisLabelPos); painter.drawText(xRect, Qt::AlignCenter, "X"); QRect yRect = fm.boundingRect("Y"); yRect.moveCenter(m_yAxisLabelPos); painter.drawText(yRect, Qt::AlignCenter, "Y"); QRect zRect = fm.boundingRect("Z"); zRect.moveCenter(m_zAxisLabelPos); painter.drawText(zRect, Qt::AlignCenter, "Z"); if (!m_isHovered) { return; } m_homeButtonRect = getHomeButtonRect(widgetWidth); if (m_homeButtonRenderer && m_homeButtonRenderer->isValid()) { m_homeButtonRenderer->render(&painter, m_homeButtonRect); } } bool ViewCube::handleMousePress(const QPoint& pos, int widgetWidth, int widgetHeight) { if (!m_isHovered) { return false; } QRect homeButtonRect = getHomeButtonRect(widgetWidth); return homeButtonRect.contains(pos); }