完成主要交互、高性能组件、国际化和A型传感器数据包接收
This commit is contained in:
273
src/glwidget.cpp
273
src/glwidget.cpp
@@ -8,13 +8,21 @@
|
||||
#include <QtMath>
|
||||
#include <QFile>
|
||||
#include <QDebug>
|
||||
#include <GL/gl.h>
|
||||
#include <qevent.h>
|
||||
#include <qlogging.h>
|
||||
#include <qminmax.h>
|
||||
#include <qopenglext.h>
|
||||
#include <qopenglshaderprogram.h>
|
||||
#include <qstringliteral.h>
|
||||
#include <qvectornd.h>
|
||||
#include <QVector3D>
|
||||
#include <QVector2D>
|
||||
#include <QVector4D>
|
||||
#include <QImage>
|
||||
#include <QPainter>
|
||||
#include <QFontMetrics>
|
||||
#include <algorithm>
|
||||
#include <limits>
|
||||
|
||||
// 读取文本文件内容(这里主要用来从 Qt Resource `:/shaders/...` 读取 shader 源码)
|
||||
@@ -77,7 +85,7 @@ void GLWidget::setPanelSize(float w, float h, float d) {
|
||||
void GLWidget::setPanelThickness(float h) {
|
||||
if (qFuzzyCompare(m_panelH, h))
|
||||
return;
|
||||
m_panelH = h;
|
||||
m_panelD = h;
|
||||
m_panelGeometryDirty = true;
|
||||
update();
|
||||
}
|
||||
@@ -91,9 +99,9 @@ void GLWidget::setSpec(int rows, int cols, float pitch, float dotRaius) {
|
||||
// 自动根据点阵尺寸调整 panel 的尺寸(保证圆点不会越过顶面边界)。
|
||||
// 约定:点中心覆盖的范围是 (cols-1)*pitch / (rows-1)*pitch,面板需要额外留出 dotRadius 的边缘空间。
|
||||
const float gridW = float(qMax(0, m_cols - 1)) * m_pitch;
|
||||
const float gridD = float(qMax(0, m_rows - 1)) * m_pitch;
|
||||
const float gridH = float(qMax(0, m_rows - 1)) * m_pitch;
|
||||
m_panelW = gridW + 2.0f * m_dotRadius;
|
||||
m_panelD = gridD + 2.0f * m_dotRadius;
|
||||
m_panelH = gridH + 2.0f * m_dotRadius;
|
||||
m_panelGeometryDirty = true;
|
||||
m_dotsGeometryDirty = true;
|
||||
|
||||
@@ -166,6 +174,23 @@ void GLWidget::setLabelModeString(const QString &mode) {
|
||||
update();
|
||||
}
|
||||
|
||||
void GLWidget::setLightMode(bool on = true) {
|
||||
if (on == m_lightMode) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_lightMode = on;
|
||||
update();
|
||||
}
|
||||
|
||||
void GLWidget::setShowBg(bool on = true) {
|
||||
if (on == m_showBg) {
|
||||
return;
|
||||
}
|
||||
m_showBg = on;
|
||||
update();
|
||||
}
|
||||
|
||||
void GLWidget::initializeGL() {
|
||||
initializeOpenGLFunctions();
|
||||
|
||||
@@ -177,6 +202,7 @@ void GLWidget::initializeGL() {
|
||||
initBackgroundGeometry_();
|
||||
initPanelGeometry_();
|
||||
initDotGeometry_();
|
||||
initRoomGeometry_();
|
||||
m_panelGeometryDirty = false;
|
||||
m_dotsGeometryDirty = false;
|
||||
|
||||
@@ -205,7 +231,7 @@ void GLWidget::paintGL() {
|
||||
}
|
||||
|
||||
// --- 背景:屏幕空间网格(不随相机旋转)---
|
||||
if (m_bgProg && m_bgVao) {
|
||||
if (m_bgProg && m_bgVao && m_showBg) {
|
||||
const float dpr = devicePixelRatioF();
|
||||
const QVector2D viewport(float(width()) * dpr, float(height()) * dpr);
|
||||
|
||||
@@ -229,6 +255,7 @@ void GLWidget::paintGL() {
|
||||
|
||||
// 1) 更新相机/投影矩阵(MVP),决定如何把 3D 世界投影到屏幕
|
||||
updateMatrices_();
|
||||
updateRoom_();
|
||||
// 2) 如果外部提交了新数据,就把 CPU 生成的 instance 数据更新到 GPU
|
||||
updateInstanceBufferIfNeeded_();
|
||||
|
||||
@@ -245,7 +272,8 @@ void GLWidget::paintGL() {
|
||||
m_panelProg->setUniformValue("uCols", m_cols);
|
||||
m_panelProg->setUniformValue("uPitch", m_pitch);
|
||||
m_panelProg->setUniformValue("uDotRadius", m_dotRadius);
|
||||
m_panelProg->setUniformValue("uRenderMode", int(m_renderMode));
|
||||
m_panelProg->setUniformValue("uRenderMode", 1);
|
||||
m_panelProg->setUniformValue("uLightMode", m_lightMode);
|
||||
glBindVertexArray(m_panelVao);
|
||||
glDrawElements(GL_TRIANGLES, m_panelIndexCount, GL_UNSIGNED_INT, nullptr);
|
||||
glBindVertexArray(0);
|
||||
@@ -257,16 +285,16 @@ void GLWidget::paintGL() {
|
||||
// uniforms:每次 draw 前设置的一组“常量参数”(对当前 draw call 的所有 instance 都一致)
|
||||
// uMVP: 同上;用于把每个 dot 的世界坐标变换到屏幕
|
||||
m_dotsProg->setUniformValue("uMVP", m_mvp);
|
||||
m_dotsProg->setUniformValue("uRenderMode", int(m_renderMode));
|
||||
m_dotsProg->setUniformValue("uRenderMode", 1);
|
||||
// uDotRadius: dot 的半径(世界坐标单位)
|
||||
m_dotsProg->setUniformValue("uDotRadius", m_dotRadius);
|
||||
// uBaseY: dot 的高度(放在 panel 顶面上方一点点,避免 z-fighting/闪烁)
|
||||
m_dotsProg->setUniformValue("uBaseY", (m_panelH * 0.5f) + 0.001f);
|
||||
m_dotsProg->setUniformValue("uBaseZ", -(m_panelD * 0.5f) - 0.001f);
|
||||
// uMinV/uMaxV: 传感值范围,用于 fragment shader 把 value 映射成颜色
|
||||
m_dotsProg->setUniformValue("uMinV", float(m_min));
|
||||
m_dotsProg->setUniformValue("uMaxV", float(m_max));
|
||||
const int hasData = (m_hasData.load() || m_dotTex == 0) ? 1 : 0;
|
||||
m_dotsProg->setUniformValue("uHasData", hasData);
|
||||
m_dotsProg->setUniformValue("uHasData", 0);
|
||||
m_dotsProg->setUniformValue("uCameraPos", m_cameraPos);
|
||||
m_dotsProg->setUniformValue("uDotTex", 0);
|
||||
if (m_dotTex) {
|
||||
@@ -282,7 +310,7 @@ void GLWidget::paintGL() {
|
||||
glBindTexture(GL_TEXTURE_2D, 0);
|
||||
}
|
||||
|
||||
m_dotsProg->release();
|
||||
// m_dotsProg->release();
|
||||
}
|
||||
|
||||
if (m_labelMode != LabelsOff && dotCount() > 0) {
|
||||
@@ -396,6 +424,25 @@ void GLWidget::mousePressEvent(QMouseEvent *event) {
|
||||
event->accept();
|
||||
return;
|
||||
}
|
||||
if (event->button() == Qt::LeftButton) {
|
||||
QVector3D world;
|
||||
const int index = pickDotIndex_(event->pos(), &world);
|
||||
if (index >= 0) {
|
||||
float value = 0.0f;
|
||||
int row = 0;
|
||||
int col = 0;
|
||||
if (m_cols > 0) {
|
||||
row = index / m_cols;
|
||||
col = index % m_cols;
|
||||
}
|
||||
{
|
||||
QMutexLocker lk(&m_dataMutex);
|
||||
if (index < m_latestValues.size())
|
||||
value = m_latestValues[index];
|
||||
}
|
||||
emit dotClicked(index, row, col, value);
|
||||
}
|
||||
}
|
||||
QOpenGLWidget::mousePressEvent(event);
|
||||
}
|
||||
|
||||
@@ -483,11 +530,75 @@ void GLWidget::mouseReleaseEvent(QMouseEvent *event) {
|
||||
void GLWidget::wheelEvent(QWheelEvent *event) {
|
||||
const float steps = event->angleDelta().y() / 120.0f;
|
||||
const float factor = std::pow(0.9f, steps);
|
||||
m_zoom_ = qBound(0.2f, m_zoom_ * factor, 45.0f);
|
||||
m_zoom_ = qBound(0.2f, m_zoom_ * factor, 90.0f);
|
||||
update();
|
||||
event->accept();
|
||||
}
|
||||
|
||||
bool GLWidget::projectToScreen_(const QVector3D& world, QPointF* out) const {
|
||||
if (!out)
|
||||
return false;
|
||||
const QVector4D clip = m_mvp * QVector4D(world, 1.0f);
|
||||
if (clip.w() <= 1e-6f)
|
||||
return false;
|
||||
const QVector3D ndc = clip.toVector3D() / clip.w();
|
||||
if (ndc.z() < -1.2f || ndc.z() > 1.2f)
|
||||
return false;
|
||||
|
||||
out->setX((ndc.x() * 0.5f + 0.5f) * float(width()));
|
||||
out->setY((1.0f - (ndc.y() * 0.5f + 0.5f)) * float(height()));
|
||||
return true;
|
||||
}
|
||||
|
||||
int GLWidget::pickDotIndex_(const QPoint& pos, QVector3D* worldOut) const {
|
||||
if (dotCount() <= 0)
|
||||
return -1;
|
||||
|
||||
const float baseY = (m_panelH * 0.5f) + 0.001f;
|
||||
const float w = (m_cols - 1) * m_pitch;
|
||||
const float h = (m_rows - 1) * m_pitch;
|
||||
|
||||
int best = -1;
|
||||
float bestDist2 = std::numeric_limits<float>::infinity();
|
||||
QVector3D bestWorld;
|
||||
|
||||
for (int i = 0; i < dotCount(); ++i) {
|
||||
const int rr = (m_cols > 0) ? (i / m_cols) : 0;
|
||||
const int cc = (m_cols > 0) ? (i % m_cols) : 0;
|
||||
const QVector3D worldCenter(
|
||||
(cc * m_pitch) - w * 0.5f,
|
||||
baseY,
|
||||
(rr * m_pitch) - h * 0.5f
|
||||
);
|
||||
|
||||
QPointF center;
|
||||
if (!projectToScreen_(worldCenter, ¢er))
|
||||
continue;
|
||||
|
||||
QPointF edge;
|
||||
if (!projectToScreen_(worldCenter + QVector3D(m_dotRadius, 0.0f, 0.0f), &edge))
|
||||
continue;
|
||||
|
||||
const float radDx = float(edge.x() - center.x());
|
||||
const float radDy = float(edge.y() - center.y());
|
||||
const float radPx = std::sqrt(radDx * radDx + radDy * radDy);
|
||||
const float threshold = radPx + 6.0f;
|
||||
|
||||
const float dx = float(pos.x()) - float(center.x());
|
||||
const float dy = float(pos.y()) - float(center.y());
|
||||
const float dist2 = dx * dx + dy * dy;
|
||||
if (dist2 <= threshold * threshold && dist2 < bestDist2) {
|
||||
best = i;
|
||||
bestDist2 = dist2;
|
||||
bestWorld = worldCenter;
|
||||
}
|
||||
}
|
||||
|
||||
if (best >= 0 && worldOut)
|
||||
*worldOut = bestWorld;
|
||||
return best;
|
||||
}
|
||||
|
||||
void GLWidget::initBackgroundGeometry_() {
|
||||
if (m_bgVbo) {
|
||||
glDeleteBuffers(1, &m_bgVbo);
|
||||
@@ -675,7 +786,6 @@ void GLWidget::initDotGeometry_() {
|
||||
|
||||
glBindVertexArray(0);
|
||||
}
|
||||
|
||||
void GLWidget::initDotTexture_() {
|
||||
if (m_dotTex) {
|
||||
glDeleteTextures(1, &m_dotTex);
|
||||
@@ -712,6 +822,91 @@ void GLWidget::initDotTexture_() {
|
||||
glBindTexture(GL_TEXTURE_2D, 0);
|
||||
}
|
||||
|
||||
void GLWidget::initRoomGeometry_() {
|
||||
qInfo() << "initRoomGeometry_()";
|
||||
if (room_vao) {
|
||||
glDeleteVertexArrays(1, &room_vao);
|
||||
room_vao = 0;
|
||||
}
|
||||
if (room_vbo) {
|
||||
glDeleteBuffers(1, &room_vbo);
|
||||
room_vbo = 0;
|
||||
}
|
||||
if (room_ibo) {
|
||||
glDeleteBuffers(1, &room_ibo);
|
||||
room_ibo = 0;
|
||||
}
|
||||
|
||||
using V = struct {
|
||||
float x, y, z;
|
||||
float nx, ny, nz;
|
||||
};
|
||||
|
||||
V verts[24] = {
|
||||
// floor (y = -1), normal +Y
|
||||
{-1, -1, -1, 0, +1, 0},
|
||||
{+1, -1, -1, 0, +1, 0},
|
||||
{+1, -1, +1, 0, +1, 0},
|
||||
{-1, -1, +1, 0, +1, 0},
|
||||
|
||||
// ceiling (y = +1), normal -Y
|
||||
{-1, +1, +1, 0, -1, 0},
|
||||
{+1, +1, +1, 0, -1, 0},
|
||||
{+1, +1, -1, 0, -1, 0},
|
||||
{-1, +1, -1, 0, -1, 0},
|
||||
|
||||
// back wall (z = -1), normal +Z
|
||||
{-1, +1, -1, 0, 0, +1},
|
||||
{+1, +1, -1, 0, 0, +1},
|
||||
{+1, -1, -1, 0, 0, +1},
|
||||
{-1, -1, -1, 0, 0, +1},
|
||||
|
||||
// front wall (z = +1), normal -Z
|
||||
{+1, +1, +1, 0, 0, -1},
|
||||
{-1, +1, +1, 0, 0, -1},
|
||||
{-1, -1, +1, 0, 0, -1},
|
||||
{+1, -1, +1, 0, 0, -1},
|
||||
|
||||
// left wall (x = -1), normal +X
|
||||
{-1, +1, +1, +1, 0, 0},
|
||||
{-1, +1, -1, +1, 0, 0},
|
||||
{-1, -1, -1, +1, 0, 0},
|
||||
{-1, -1, +1, +1, 0, 0},
|
||||
|
||||
// right wall (x = +1), normal -X
|
||||
{+1, +1, -1, -1, 0, 0},
|
||||
{+1, +1, +1, -1, 0, 0},
|
||||
{+1, -1, +1, -1, 0, 0},
|
||||
{+1, -1, -1, -1, 0, 0},
|
||||
};
|
||||
unsigned int idx[36] = {
|
||||
0, 1, 2, 0, 2, 3,
|
||||
4, 5, 6, 4, 6, 7,
|
||||
8, 9, 10, 8, 10, 11,
|
||||
12, 13, 14, 12, 14, 15,
|
||||
16, 17, 18, 16, 18, 19,
|
||||
20, 21, 22, 20, 22, 23
|
||||
};
|
||||
room_index_count = 36;
|
||||
|
||||
glGenVertexArrays(1, &room_vao);
|
||||
glBindVertexArray(room_vao);
|
||||
|
||||
glGenBuffers(1, &room_vbo);
|
||||
glBindBuffer(GL_ARRAY_BUFFER, room_vbo);
|
||||
glBufferData(GL_ARRAY_BUFFER, sizeof(verts), verts, GL_STATIC_DRAW);
|
||||
|
||||
glGenBuffers(1, &room_ibo);
|
||||
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, room_ibo);
|
||||
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(idx), idx, GL_STATIC_DRAW);
|
||||
|
||||
glEnableVertexAttribArray(0);
|
||||
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(V), (void*)0);
|
||||
glEnableVertexAttribArray(1);
|
||||
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, sizeof(V), (void*)(3 * sizeof(float)));
|
||||
|
||||
glBindVertexArray(0);
|
||||
}
|
||||
void GLWidget::initPrograms_() {
|
||||
// Qt Resource 里打包的 shader 文件路径:使用 `:/` 前缀(不是文件系统路径)
|
||||
const QString vsd_path = QStringLiteral(":/shaders/dots.vert");
|
||||
@@ -720,6 +915,8 @@ void GLWidget::initPrograms_() {
|
||||
const QString fsb_path = QStringLiteral(":/shaders/bg.frag");
|
||||
const QString vsp_path = QStringLiteral(":/shaders/panel.vert");
|
||||
const QString fsp_path = QStringLiteral(":/shaders/panel.frag");
|
||||
const QString vsr_path = QStringLiteral(":/shaders/room.vert");
|
||||
const QString fsr_path = QStringLiteral(":/shaders/room.frag");
|
||||
|
||||
{
|
||||
auto *p = new QOpenGLShaderProgram;
|
||||
@@ -757,6 +954,18 @@ void GLWidget::initPrograms_() {
|
||||
}
|
||||
m_dotsProg = p;
|
||||
}
|
||||
{
|
||||
auto *p = new QOpenGLShaderProgram;
|
||||
const bool okV = p->addShaderFromSourceCode(QOpenGLShader::Vertex, readFile(vsr_path));
|
||||
const bool okF = p->addShaderFromSourceCode(QOpenGLShader::Fragment, readFile(fsr_path));
|
||||
const bool okL = okV && okF && p->link();
|
||||
if (!okL) {
|
||||
qWarning() << "dots program build failed:" << vsr_path << fsr_path << p->log();
|
||||
delete p;
|
||||
p = nullptr;
|
||||
}
|
||||
m_roomProg = p;
|
||||
}
|
||||
}
|
||||
|
||||
void GLWidget::updateInstanceBufferIfNeeded_() {
|
||||
@@ -825,7 +1034,7 @@ void GLWidget::updateMatrices_() {
|
||||
const float distance = qMax(0.5f, radius * 2.5f);
|
||||
|
||||
// 让相机看向 panel 的中心点
|
||||
const QVector3D center(0.0f, m_panelH * 0.5f, 0.0f);
|
||||
const QVector3D center(0.0f, 0.0f, 0.0f);
|
||||
|
||||
// yaw/pitch 控制相机绕目标点“环绕”(orbit camera)
|
||||
const float yawRad = qDegreesToRadians(m_camYawDeg);
|
||||
@@ -849,3 +1058,43 @@ void GLWidget::updateMatrices_() {
|
||||
|
||||
m_mvp = proj * view;
|
||||
}
|
||||
|
||||
void GLWidget::updateRoom_(){
|
||||
if (!m_roomProg || !room_vao || room_index_count <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_roomProg->bind();
|
||||
|
||||
const float base = std::max({ m_panelW, m_panelH, m_panelD });
|
||||
const QVector3D roomHalfSize(
|
||||
std::max(1.0f, base * 1.5f),
|
||||
std::max(1.0f, base * 1.5f),
|
||||
std::max(1.0f, base * 1.5f)
|
||||
);
|
||||
|
||||
const float minorStep = std::max(0.05f, base * 0.25f);
|
||||
const float majorStep = minorStep * 5.0f;
|
||||
|
||||
m_roomProg->setUniformValue("uMVP", m_mvp);
|
||||
m_roomProg->setUniformValue("uCameraPos", m_cameraPos);
|
||||
m_roomProg->setUniformValue("uRoomHalfSize", roomHalfSize);
|
||||
m_roomProg->setUniformValue("uMinorStep", minorStep);
|
||||
m_roomProg->setUniformValue("uMajorStep", majorStep);
|
||||
m_roomProg->setUniformValue("uRenderMode", 1);
|
||||
m_roomProg->setUniformValue("uLightMode", m_lightMode);
|
||||
m_roomProg->setUniformValue("uShowGrid", m_showGrid);
|
||||
|
||||
glBindVertexArray(room_vao);
|
||||
glDrawElements(GL_TRIANGLES, room_index_count, GL_UNSIGNED_INT, nullptr);
|
||||
glBindVertexArray(0);
|
||||
m_roomProg->release();
|
||||
}
|
||||
|
||||
void GLWidget::setShowGrid(bool on) {
|
||||
if (m_showGrid == on)
|
||||
return;
|
||||
|
||||
m_showGrid = on;
|
||||
update();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user