300 lines
9.5 KiB
C++
300 lines
9.5 KiB
C++
#include "ViewCube.h"
|
|
#include <QGuiApplication>
|
|
#include <QPainter>
|
|
#include <QFont>
|
|
#include <QOpenGLShaderProgram>
|
|
#include <QOpenGLTexture>
|
|
#include <QScreen>
|
|
#include <QSvgRenderer>
|
|
#include <QVector>
|
|
#include <QVector3D>
|
|
|
|
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);
|
|
}
|