feat:3D multi dot
This commit is contained in:
@@ -7,6 +7,7 @@
|
||||
|
||||
#include <opencv2/core.hpp>
|
||||
#include <opencv2/imgproc.hpp>
|
||||
#include <algorithm>
|
||||
|
||||
class GlobalHelper {
|
||||
public:
|
||||
@@ -17,7 +18,7 @@ public:
|
||||
}
|
||||
|
||||
static void transToMultiMatrix(const cv::Mat& raw_data, float min_threshold,
|
||||
float max_range, cv::Size display_res, cv::Mat& out_image) {
|
||||
float max_range, cv::Size display_res, cv::Mat& out_matrix) {
|
||||
CV_Assert(raw_data.type() == CV_32F);
|
||||
|
||||
cv::Mat raw = raw_data.clone();
|
||||
@@ -29,7 +30,8 @@ public:
|
||||
cv::GaussianBlur(raw, raw, cv::Size(3, 3), 0.8, 0.8, cv::BORDER_DEFAULT);
|
||||
}
|
||||
|
||||
cv::Mat norm_data = raw / max_range;
|
||||
const float safe_range = std::max(max_range, 1e-6f);
|
||||
cv::Mat norm_data = raw / safe_range;
|
||||
cv::min(norm_data, 1.0f, norm_data);
|
||||
cv::max(norm_data, 0.0f, norm_data);
|
||||
|
||||
@@ -40,10 +42,11 @@ public:
|
||||
|
||||
cv::GaussianBlur(smoothed, smoothed, cv::Size(31, 31), 0.0, 0.0, cv::BORDER_DEFAULT);
|
||||
|
||||
cv::transpose(smoothed, out_image);
|
||||
smoothed *= safe_range;
|
||||
out_matrix = smoothed;
|
||||
}
|
||||
private:
|
||||
GlobalHelper() {}
|
||||
};
|
||||
|
||||
#endif //TACTILEIPC3D_GLOBALHELPER_H
|
||||
#endif //TACTILEIPC3D_GLOBALHELPER_H
|
||||
|
||||
561
src/glwidget.cpp
561
src/glwidget.cpp
@@ -21,8 +21,12 @@
|
||||
#include <QImage>
|
||||
#include <QPainter>
|
||||
#include <QFontMetrics>
|
||||
#include <QPen>
|
||||
#include <algorithm>
|
||||
#include <limits>
|
||||
#include <cstring>
|
||||
#include <cmath>
|
||||
#include "globalhelper.h"
|
||||
|
||||
// 读取文本文件内容(这里主要用来从 Qt Resource `:/shaders/...` 读取 shader 源码)
|
||||
static QByteArray readFile(const QString& path) {
|
||||
@@ -54,6 +58,8 @@ GLWidget::~GLWidget() {
|
||||
delete m_bgProg;
|
||||
delete m_panelProg;
|
||||
delete m_dotsProg;
|
||||
delete m_heatmapProg;
|
||||
delete m_baseProg;
|
||||
|
||||
if (m_panelIbo)
|
||||
glDeleteBuffers(1, &m_panelIbo);
|
||||
@@ -74,6 +80,26 @@ GLWidget::~GLWidget() {
|
||||
|
||||
if (m_dotTex)
|
||||
glDeleteTextures(1, &m_dotTex);
|
||||
if (m_heatmapVao)
|
||||
glDeleteVertexArrays(1, &m_heatmapVao);
|
||||
if (m_heatmapVbo)
|
||||
glDeleteBuffers(1, &m_heatmapVbo);
|
||||
if (m_heatmapIbo)
|
||||
glDeleteBuffers(1, &m_heatmapIbo);
|
||||
if (m_baseVao)
|
||||
glDeleteVertexArrays(1, &m_baseVao);
|
||||
if (m_baseVbo)
|
||||
glDeleteBuffers(1, &m_baseVbo);
|
||||
if (m_baseIbo)
|
||||
glDeleteBuffers(1, &m_baseIbo);
|
||||
if (m_skirtVao)
|
||||
glDeleteVertexArrays(1, &m_skirtVao);
|
||||
if (m_skirtVbo)
|
||||
glDeleteBuffers(1, &m_skirtVbo);
|
||||
if (m_skirtIbo)
|
||||
glDeleteBuffers(1, &m_skirtIbo);
|
||||
if (m_heightTex)
|
||||
glDeleteTextures(1, &m_heightTex);
|
||||
doneCurrent();
|
||||
}
|
||||
|
||||
@@ -82,6 +108,7 @@ void GLWidget::setPanelSize(float w, float h, float d) {
|
||||
m_panelH = h;
|
||||
m_panelD = d;
|
||||
m_panelGeometryDirty = true;
|
||||
m_heightDirty = true;
|
||||
update();
|
||||
}
|
||||
|
||||
@@ -90,6 +117,7 @@ void GLWidget::setPanelThickness(float h) {
|
||||
return;
|
||||
m_panelD = h;
|
||||
m_panelGeometryDirty = true;
|
||||
m_heightDirty = true;
|
||||
update();
|
||||
}
|
||||
|
||||
@@ -115,6 +143,7 @@ void GLWidget::setSpec(int rows, int cols, float pitch, float dotRaius) {
|
||||
}
|
||||
m_valuesDirty = true;
|
||||
m_hasData = false;
|
||||
m_heightDirty = true;
|
||||
update();
|
||||
}
|
||||
|
||||
@@ -126,6 +155,7 @@ void GLWidget::submitValues(const QVector<float> &values) {
|
||||
m_latestValues = values;
|
||||
m_valuesDirty = true;
|
||||
m_hasData = true;
|
||||
m_heightDirty = true;
|
||||
}
|
||||
|
||||
update();
|
||||
@@ -134,6 +164,7 @@ void GLWidget::submitValues(const QVector<float> &values) {
|
||||
void GLWidget::setRange(int minV, int maxV) {
|
||||
m_min = minV;
|
||||
m_max = maxV;
|
||||
m_heightDirty = true;
|
||||
update();
|
||||
}
|
||||
|
||||
@@ -230,6 +261,12 @@ void GLWidget::initializeGL() {
|
||||
initPanelGeometry_();
|
||||
initDotGeometry_();
|
||||
initRoomGeometry_();
|
||||
initBaseGeometry_();
|
||||
initHeatmapGeometry_();
|
||||
initSkirtGeometry_();
|
||||
updateHeatmapData_();
|
||||
initHeightTexture_();
|
||||
updateSkirtVertices_();
|
||||
m_panelGeometryDirty = false;
|
||||
m_dotsGeometryDirty = false;
|
||||
|
||||
@@ -244,6 +281,12 @@ void GLWidget::initGeometry_() {
|
||||
initPanelGeometry_();
|
||||
initDotGeometry_();
|
||||
initRoomGeometry_();
|
||||
initBaseGeometry_();
|
||||
initHeatmapGeometry_();
|
||||
initSkirtGeometry_();
|
||||
updateHeatmapData_();
|
||||
initHeightTexture_();
|
||||
updateSkirtVertices_();
|
||||
}
|
||||
|
||||
void GLWidget::resizeGL(int w, int h) {
|
||||
@@ -257,6 +300,10 @@ void GLWidget::paintGL() {
|
||||
// 如果点阵规格/面板尺寸发生变化,需要在有 GL 上下文时重建几何体 buffer。
|
||||
if (m_panelGeometryDirty) {
|
||||
initPanelGeometry_();
|
||||
initBaseGeometry_();
|
||||
initHeatmapGeometry_();
|
||||
initSkirtGeometry_();
|
||||
m_heightDirty = true;
|
||||
m_panelGeometryDirty = false;
|
||||
}
|
||||
if (m_dotsGeometryDirty) {
|
||||
@@ -291,10 +338,19 @@ void GLWidget::paintGL() {
|
||||
// 1) 更新相机/投影矩阵(MVP),决定如何把 3D 世界投影到屏幕
|
||||
updateMatrices_();
|
||||
updateRoom_();
|
||||
// 2) 如果外部提交了新数据,就把 CPU 生成的 instance 数据更新到 GPU
|
||||
updateInstanceBufferIfNeeded_();
|
||||
if (m_useHeatmap) {
|
||||
updateHeatmapData_();
|
||||
uploadHeightTexture_();
|
||||
updateSkirtVertices_();
|
||||
renderBase_();
|
||||
renderSkirt_();
|
||||
renderHeatmap_();
|
||||
} else {
|
||||
// 2) 如果外部提交了新数据,就把 CPU 生成的 instance 数据更新到 GPU
|
||||
updateInstanceBufferIfNeeded_();
|
||||
}
|
||||
|
||||
if (m_panelProg) {
|
||||
if (!m_useHeatmap && m_panelProg) {
|
||||
m_panelProg->bind();
|
||||
// uniforms:每次 draw 前设置的一组“常量参数”(对当前 draw call 的所有顶点/片元都一致)
|
||||
// uMVP: Model-View-Projection 矩阵(把顶点从世界坐标 -> 裁剪空间;gl_Position 必须输出裁剪空间坐标)
|
||||
@@ -315,7 +371,7 @@ void GLWidget::paintGL() {
|
||||
m_panelProg->release();
|
||||
}
|
||||
|
||||
if (m_dotsProg) {
|
||||
if (!m_useHeatmap && m_dotsProg) {
|
||||
m_dotsProg->bind();
|
||||
// uniforms:每次 draw 前设置的一组“常量参数”(对当前 draw call 的所有 instance 都一致)
|
||||
// uMVP: 同上;用于把每个 dot 的世界坐标变换到屏幕
|
||||
@@ -351,7 +407,7 @@ void GLWidget::paintGL() {
|
||||
// m_dotsProg->release();
|
||||
}
|
||||
|
||||
if (m_labelMode != LabelsOff && dotCount() > 0) {
|
||||
if (!m_useHeatmap && m_labelMode != LabelsOff && dotCount() > 0) {
|
||||
QVector<float> valuesCopy;
|
||||
{
|
||||
QMutexLocker lk(&m_dataMutex);
|
||||
@@ -453,6 +509,9 @@ void GLWidget::paintGL() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QPainter uiPainter(this);
|
||||
drawToggleButton_(&uiPainter);
|
||||
}
|
||||
|
||||
void GLWidget::mousePressEvent(QMouseEvent *event) {
|
||||
@@ -463,6 +522,16 @@ void GLWidget::mousePressEvent(QMouseEvent *event) {
|
||||
return;
|
||||
}
|
||||
if (event->button() == Qt::LeftButton) {
|
||||
if (m_toggleRect.contains(event->pos())) {
|
||||
m_useHeatmap = !m_useHeatmap;
|
||||
update();
|
||||
event->accept();
|
||||
return;
|
||||
}
|
||||
if (m_useHeatmap) {
|
||||
event->accept();
|
||||
return;
|
||||
}
|
||||
QVector3D world;
|
||||
const int index = pickDotIndex_(event->pos(), &world);
|
||||
qInfo() << "clicked index: " << index;
|
||||
@@ -498,7 +567,7 @@ void GLWidget::mouseMoveEvent(QMouseEvent *event) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_labelMode != LabelsOff && dotCount() > 0) {
|
||||
if (!m_useHeatmap && m_labelMode != LabelsOff && dotCount() > 0) {
|
||||
auto projectToScreen = [&](const QVector3D& world, QPointF& out) -> bool {
|
||||
const QVector4D clip = m_mvp * QVector4D(world, 1.0f);
|
||||
if (clip.w() <= 1e-6f)
|
||||
@@ -946,6 +1015,458 @@ void GLWidget::initRoomGeometry_() {
|
||||
|
||||
glBindVertexArray(0);
|
||||
}
|
||||
|
||||
void GLWidget::initBaseGeometry_() {
|
||||
if (m_baseIbo) {
|
||||
glDeleteBuffers(1, &m_baseIbo);
|
||||
m_baseIbo = 0;
|
||||
}
|
||||
if (m_baseVbo) {
|
||||
glDeleteBuffers(1, &m_baseVbo);
|
||||
m_baseVbo = 0;
|
||||
}
|
||||
if (m_baseVao) {
|
||||
glDeleteVertexArrays(1, &m_baseVao);
|
||||
m_baseVao = 0;
|
||||
}
|
||||
|
||||
const float hw = m_panelW * 0.5f;
|
||||
const float hh = m_panelH * 0.5f;
|
||||
const float z = m_panelD * 0.5f;
|
||||
|
||||
struct V {
|
||||
float x, y, z;
|
||||
float nx, ny, nz;
|
||||
};
|
||||
V verts[4] = {
|
||||
{-hw, -hh, z, 0, 0, -1},
|
||||
{+hw, -hh, z, 0, 0, -1},
|
||||
{+hw, +hh, z, 0, 0, -1},
|
||||
{-hw, +hh, z, 0, 0, -1},
|
||||
};
|
||||
unsigned int idx[6] = {0, 1, 2, 0, 2, 3};
|
||||
m_baseIndexCount = 6;
|
||||
|
||||
glGenVertexArrays(1, &m_baseVao);
|
||||
glBindVertexArray(m_baseVao);
|
||||
|
||||
glGenBuffers(1, &m_baseVbo);
|
||||
glBindBuffer(GL_ARRAY_BUFFER, m_baseVbo);
|
||||
glBufferData(GL_ARRAY_BUFFER, sizeof(verts), verts, GL_STATIC_DRAW);
|
||||
|
||||
glGenBuffers(1, &m_baseIbo);
|
||||
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_baseIbo);
|
||||
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::initHeatmapGeometry_() {
|
||||
if (m_heatmapIbo) {
|
||||
glDeleteBuffers(1, &m_heatmapIbo);
|
||||
m_heatmapIbo = 0;
|
||||
}
|
||||
if (m_heatmapVbo) {
|
||||
glDeleteBuffers(1, &m_heatmapVbo);
|
||||
m_heatmapVbo = 0;
|
||||
}
|
||||
if (m_heatmapVao) {
|
||||
glDeleteVertexArrays(1, &m_heatmapVao);
|
||||
m_heatmapVao = 0;
|
||||
}
|
||||
|
||||
const float aspect = (m_panelW > 1e-6f) ? (m_panelH / m_panelW) : 1.0f;
|
||||
const int cols = qMax(2, qMin(m_cols * m_upscale, 240));
|
||||
int rows = qMax(2, int(cols * aspect + 0.5f));
|
||||
rows = qMax(2, qMin(rows, 240));
|
||||
m_heatmapCols = cols;
|
||||
m_heatmapRows = rows;
|
||||
|
||||
const int vertex_count = cols * rows;
|
||||
QVector<float> verts;
|
||||
verts.reserve(vertex_count * 5);
|
||||
|
||||
for (int r = 0; r < rows; ++r) {
|
||||
const float v = (rows > 1) ? float(r) / float(rows - 1) : 0.0f;
|
||||
const float y = (v - 0.5f) * m_panelH;
|
||||
for (int c = 0; c < cols; ++c) {
|
||||
const float u = (cols > 1) ? float(c) / float(cols - 1) : 0.0f;
|
||||
const float x = (u - 0.5f) * m_panelW;
|
||||
verts.push_back(x);
|
||||
verts.push_back(y);
|
||||
verts.push_back(0.0f);
|
||||
verts.push_back(u);
|
||||
verts.push_back(v);
|
||||
}
|
||||
}
|
||||
|
||||
QVector<unsigned int> idx;
|
||||
idx.reserve((cols - 1) * (rows - 1) * 6);
|
||||
for (int r = 0; r < rows - 1; ++r) {
|
||||
for (int c = 0; c < cols - 1; ++c) {
|
||||
const unsigned int i0 = unsigned(r * cols + c);
|
||||
const unsigned int i1 = unsigned(r * cols + c + 1);
|
||||
const unsigned int i2 = unsigned((r + 1) * cols + c + 1);
|
||||
const unsigned int i3 = unsigned((r + 1) * cols + c);
|
||||
idx.push_back(i0);
|
||||
idx.push_back(i1);
|
||||
idx.push_back(i2);
|
||||
idx.push_back(i0);
|
||||
idx.push_back(i2);
|
||||
idx.push_back(i3);
|
||||
}
|
||||
}
|
||||
m_heatmapIndexCount = int(idx.size());
|
||||
|
||||
glGenVertexArrays(1, &m_heatmapVao);
|
||||
glBindVertexArray(m_heatmapVao);
|
||||
|
||||
glGenBuffers(1, &m_heatmapVbo);
|
||||
glBindBuffer(GL_ARRAY_BUFFER, m_heatmapVbo);
|
||||
glBufferData(GL_ARRAY_BUFFER, verts.size() * sizeof(float), verts.constData(), GL_STATIC_DRAW);
|
||||
|
||||
glGenBuffers(1, &m_heatmapIbo);
|
||||
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_heatmapIbo);
|
||||
glBufferData(GL_ELEMENT_ARRAY_BUFFER, idx.size() * sizeof(unsigned int), idx.constData(), GL_STATIC_DRAW);
|
||||
|
||||
glEnableVertexAttribArray(0);
|
||||
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)0);
|
||||
glEnableVertexAttribArray(1);
|
||||
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)(3 * sizeof(float)));
|
||||
|
||||
glBindVertexArray(0);
|
||||
}
|
||||
|
||||
void GLWidget::initSkirtGeometry_() {
|
||||
if (m_skirtIbo) {
|
||||
glDeleteBuffers(1, &m_skirtIbo);
|
||||
m_skirtIbo = 0;
|
||||
}
|
||||
if (m_skirtVbo) {
|
||||
glDeleteBuffers(1, &m_skirtVbo);
|
||||
m_skirtVbo = 0;
|
||||
}
|
||||
if (m_skirtVao) {
|
||||
glDeleteVertexArrays(1, &m_skirtVao);
|
||||
m_skirtVao = 0;
|
||||
}
|
||||
|
||||
m_skirtUVs.clear();
|
||||
m_skirtNormals.clear();
|
||||
|
||||
const int cols = qMax(2, m_heatmapCols);
|
||||
const int rows = qMax(2, m_heatmapRows);
|
||||
|
||||
auto push_border = [&](int c, int r, const QVector3D& n) {
|
||||
const float u = (cols > 1) ? float(c) / float(cols - 1) : 0.0f;
|
||||
const float v = (rows > 1) ? float(r) / float(rows - 1) : 0.0f;
|
||||
m_skirtUVs.push_back(QVector2D(u, v));
|
||||
m_skirtNormals.push_back(n);
|
||||
};
|
||||
|
||||
for (int c = 0; c < cols; ++c) {
|
||||
push_border(c, 0, QVector3D(0.0f, -1.0f, 0.0f));
|
||||
}
|
||||
for (int r = 1; r < rows - 1; ++r) {
|
||||
push_border(cols - 1, r, QVector3D(1.0f, 0.0f, 0.0f));
|
||||
}
|
||||
for (int c = cols - 1; c >= 0; --c) {
|
||||
push_border(c, rows - 1, QVector3D(0.0f, 1.0f, 0.0f));
|
||||
}
|
||||
for (int r = rows - 2; r >= 1; --r) {
|
||||
push_border(0, r, QVector3D(-1.0f, 0.0f, 0.0f));
|
||||
}
|
||||
|
||||
const int border_count = m_skirtUVs.size();
|
||||
m_skirtVertices.fill(0.0f, border_count * 2 * 6);
|
||||
|
||||
QVector<unsigned int> idx;
|
||||
idx.reserve(border_count * 6);
|
||||
for (int i = 0; i < border_count; ++i) {
|
||||
const int next = (i + 1) % border_count;
|
||||
const unsigned int top0 = unsigned(i * 2);
|
||||
const unsigned int bot0 = unsigned(i * 2 + 1);
|
||||
const unsigned int top1 = unsigned(next * 2);
|
||||
const unsigned int bot1 = unsigned(next * 2 + 1);
|
||||
|
||||
idx.push_back(top0);
|
||||
idx.push_back(top1);
|
||||
idx.push_back(bot1);
|
||||
idx.push_back(top0);
|
||||
idx.push_back(bot1);
|
||||
idx.push_back(bot0);
|
||||
}
|
||||
m_skirtIndexCount = int(idx.size());
|
||||
|
||||
glGenVertexArrays(1, &m_skirtVao);
|
||||
glBindVertexArray(m_skirtVao);
|
||||
|
||||
glGenBuffers(1, &m_skirtVbo);
|
||||
glBindBuffer(GL_ARRAY_BUFFER, m_skirtVbo);
|
||||
glBufferData(GL_ARRAY_BUFFER, m_skirtVertices.size() * sizeof(float), m_skirtVertices.constData(), GL_DYNAMIC_DRAW);
|
||||
|
||||
glGenBuffers(1, &m_skirtIbo);
|
||||
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_skirtIbo);
|
||||
glBufferData(GL_ELEMENT_ARRAY_BUFFER, idx.size() * sizeof(unsigned int), idx.constData(), GL_STATIC_DRAW);
|
||||
|
||||
glEnableVertexAttribArray(0);
|
||||
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)0);
|
||||
glEnableVertexAttribArray(1);
|
||||
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)(3 * sizeof(float)));
|
||||
|
||||
glBindVertexArray(0);
|
||||
}
|
||||
|
||||
void GLWidget::initHeightTexture_() {
|
||||
if (m_heightW <= 0 || m_heightH <= 0 || m_heightValues.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
if (m_heightTex) {
|
||||
glDeleteTextures(1, &m_heightTex);
|
||||
m_heightTex = 0;
|
||||
}
|
||||
glGenTextures(1, &m_heightTex);
|
||||
glBindTexture(GL_TEXTURE_2D, m_heightTex);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
||||
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_R32F, m_heightW, m_heightH, 0, GL_RED, GL_FLOAT, m_heightValues.constData());
|
||||
m_heightTexW = m_heightW;
|
||||
m_heightTexH = m_heightH;
|
||||
glBindTexture(GL_TEXTURE_2D, 0);
|
||||
}
|
||||
|
||||
float GLWidget::sampleHeightValue_(float u, float v) const {
|
||||
if (m_heightW <= 0 || m_heightH <= 0 || m_heightValues.isEmpty()) {
|
||||
return float(m_min);
|
||||
}
|
||||
const float x = qBound(0.0f, u, 1.0f) * float(m_heightW - 1);
|
||||
const float y = qBound(0.0f, v, 1.0f) * float(m_heightH - 1);
|
||||
const int x0 = qBound(0, int(std::floor(x)), m_heightW - 1);
|
||||
const int y0 = qBound(0, int(std::floor(y)), m_heightH - 1);
|
||||
const int x1 = qMin(x0 + 1, m_heightW - 1);
|
||||
const int y1 = qMin(y0 + 1, m_heightH - 1);
|
||||
const float fx = x - float(x0);
|
||||
const float fy = y - float(y0);
|
||||
|
||||
const float v00 = m_heightValues[y0 * m_heightW + x0];
|
||||
const float v10 = m_heightValues[y0 * m_heightW + x1];
|
||||
const float v01 = m_heightValues[y1 * m_heightW + x0];
|
||||
const float v11 = m_heightValues[y1 * m_heightW + x1];
|
||||
|
||||
const float vx0 = v00 + (v10 - v00) * fx;
|
||||
const float vx1 = v01 + (v11 - v01) * fx;
|
||||
return vx0 + (vx1 - vx0) * fy;
|
||||
}
|
||||
|
||||
void GLWidget::updateHeatmapData_() {
|
||||
if (!m_heightDirty && !m_heightValues.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
if (m_rows <= 0 || m_cols <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
QVector<float> valuesCopy;
|
||||
{
|
||||
QMutexLocker lk(&m_dataMutex);
|
||||
valuesCopy = m_latestValues;
|
||||
}
|
||||
|
||||
cv::Mat raw(m_rows, m_cols, CV_32F);
|
||||
for (int r = 0; r < m_rows; ++r) {
|
||||
for (int c = 0; c < m_cols; ++c) {
|
||||
const int index = r * m_cols + c;
|
||||
const float v = (index < valuesCopy.size()) ? valuesCopy[index] : float(m_min);
|
||||
raw.at<float>(r, c) = v;
|
||||
}
|
||||
}
|
||||
|
||||
const int outW = qMax(1, m_cols * m_upscale);
|
||||
const int outH = qMax(1, m_rows * m_upscale);
|
||||
cv::Mat out;
|
||||
GlobalHelper::transToMultiMatrix(raw, float(m_min), float(m_max), cv::Size(outW, outH), out);
|
||||
if (out.empty()) {
|
||||
return;
|
||||
}
|
||||
if (out.type() != CV_32F) {
|
||||
out.convertTo(out, CV_32F);
|
||||
}
|
||||
|
||||
const int nextW = out.cols;
|
||||
const int nextH = out.rows;
|
||||
if (nextW <= 0 || nextH <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_heightW = nextW;
|
||||
m_heightH = nextH;
|
||||
m_heightValues.resize(m_heightW * m_heightH);
|
||||
|
||||
if (out.isContinuous()) {
|
||||
const size_t count = size_t(m_heightW) * size_t(m_heightH);
|
||||
std::memcpy(m_heightValues.data(), out.ptr<float>(0), count * sizeof(float));
|
||||
} else {
|
||||
for (int r = 0; r < m_heightH; ++r) {
|
||||
const float* rowPtr = out.ptr<float>(r);
|
||||
std::memcpy(m_heightValues.data() + r * m_heightW, rowPtr, m_heightW * sizeof(float));
|
||||
}
|
||||
}
|
||||
m_heightDirty = false;
|
||||
}
|
||||
|
||||
void GLWidget::uploadHeightTexture_() {
|
||||
if (m_heightValues.isEmpty() || m_heightW <= 0 || m_heightH <= 0) {
|
||||
return;
|
||||
}
|
||||
if (!m_heightTex || m_heightTexW != m_heightW || m_heightTexH != m_heightH) {
|
||||
initHeightTexture_();
|
||||
}
|
||||
if (!m_heightTex) {
|
||||
return;
|
||||
}
|
||||
|
||||
glBindTexture(GL_TEXTURE_2D, m_heightTex);
|
||||
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
|
||||
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, m_heightW, m_heightH, GL_RED, GL_FLOAT, m_heightValues.constData());
|
||||
glBindTexture(GL_TEXTURE_2D, 0);
|
||||
}
|
||||
|
||||
void GLWidget::updateSkirtVertices_() {
|
||||
if (m_skirtVertices.isEmpty() || m_skirtUVs.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
const float baseZ = -m_panelD * 0.5f;
|
||||
const float backZ = baseZ + m_panelD;
|
||||
const float minV = float(m_min);
|
||||
const float maxV = float(m_max);
|
||||
const float invRange = 1.0f / qMax(1e-6f, (maxV - minV));
|
||||
const float dentMax = qMin(m_dentMax, m_panelD);
|
||||
|
||||
const int border_count = m_skirtUVs.size();
|
||||
for (int i = 0; i < border_count; ++i) {
|
||||
const float u = m_skirtUVs[i].x();
|
||||
const float v = m_skirtUVs[i].y();
|
||||
const float value = sampleHeightValue_(u, v);
|
||||
const float t = qBound(0.0f, (value - minV) * invRange, 1.0f);
|
||||
const float h = t * dentMax;
|
||||
|
||||
const float x = (u - 0.5f) * m_panelW;
|
||||
const float y = (v - 0.5f) * m_panelH;
|
||||
const QVector3D n = m_skirtNormals[i];
|
||||
|
||||
const int topIndex = i * 2;
|
||||
const int botIndex = i * 2 + 1;
|
||||
|
||||
const int topBase = topIndex * 6;
|
||||
m_skirtVertices[topBase + 0] = x;
|
||||
m_skirtVertices[topBase + 1] = y;
|
||||
m_skirtVertices[topBase + 2] = baseZ + h;
|
||||
m_skirtVertices[topBase + 3] = n.x();
|
||||
m_skirtVertices[topBase + 4] = n.y();
|
||||
m_skirtVertices[topBase + 5] = n.z();
|
||||
|
||||
const int botBase = botIndex * 6;
|
||||
m_skirtVertices[botBase + 0] = x;
|
||||
m_skirtVertices[botBase + 1] = y;
|
||||
m_skirtVertices[botBase + 2] = backZ;
|
||||
m_skirtVertices[botBase + 3] = n.x();
|
||||
m_skirtVertices[botBase + 4] = n.y();
|
||||
m_skirtVertices[botBase + 5] = n.z();
|
||||
}
|
||||
|
||||
glBindBuffer(GL_ARRAY_BUFFER, m_skirtVbo);
|
||||
glBufferSubData(GL_ARRAY_BUFFER, 0, m_skirtVertices.size() * sizeof(float), m_skirtVertices.constData());
|
||||
}
|
||||
|
||||
void GLWidget::renderBase_() {
|
||||
if (!m_baseProg || !m_baseVao) {
|
||||
return;
|
||||
}
|
||||
m_baseProg->bind();
|
||||
m_baseProg->setUniformValue("uMVP", m_mvp);
|
||||
m_baseProg->setUniformValue("uCameraPos", m_cameraPos);
|
||||
m_baseProg->setUniformValue("uLightDir", QVector3D(-0.25f, 0.35f, -1.0f).normalized());
|
||||
m_baseProg->setUniformValue("uColor", m_colorLow);
|
||||
|
||||
glBindVertexArray(m_baseVao);
|
||||
glDrawElements(GL_TRIANGLES, m_baseIndexCount, GL_UNSIGNED_INT, nullptr);
|
||||
glBindVertexArray(0);
|
||||
m_baseProg->release();
|
||||
}
|
||||
|
||||
void GLWidget::renderSkirt_() {
|
||||
if (!m_baseProg || !m_skirtVao || m_skirtIndexCount <= 0) {
|
||||
return;
|
||||
}
|
||||
m_baseProg->bind();
|
||||
m_baseProg->setUniformValue("uMVP", m_mvp);
|
||||
m_baseProg->setUniformValue("uCameraPos", m_cameraPos);
|
||||
m_baseProg->setUniformValue("uLightDir", QVector3D(-0.25f, 0.35f, -1.0f).normalized());
|
||||
m_baseProg->setUniformValue("uColor", m_colorLow);
|
||||
|
||||
glBindVertexArray(m_skirtVao);
|
||||
glDrawElements(GL_TRIANGLES, m_skirtIndexCount, GL_UNSIGNED_INT, nullptr);
|
||||
glBindVertexArray(0);
|
||||
m_baseProg->release();
|
||||
}
|
||||
|
||||
void GLWidget::renderHeatmap_() {
|
||||
if (!m_heatmapProg || !m_heatmapVao || m_heatmapIndexCount <= 0 || !m_heightTex) {
|
||||
return;
|
||||
}
|
||||
m_heatmapProg->bind();
|
||||
m_heatmapProg->setUniformValue("uMVP", m_mvp);
|
||||
m_heatmapProg->setUniformValue("uMinV", float(m_min));
|
||||
m_heatmapProg->setUniformValue("uMaxV", float(m_max));
|
||||
m_heatmapProg->setUniformValue("uHeightScale", qMin(m_dentMax, m_panelD));
|
||||
m_heatmapProg->setUniformValue("uBaseZ", -m_panelD * 0.5f);
|
||||
m_heatmapProg->setUniformValue("uTexelSize", QVector2D(1.0f / float(m_heightW), 1.0f / float(m_heightH)));
|
||||
m_heatmapProg->setUniformValue("uPlaneSize", QVector2D(m_panelW, m_panelH));
|
||||
m_heatmapProg->setUniformValue("uCameraPos", m_cameraPos);
|
||||
m_heatmapProg->setUniformValue("uLightDir", QVector3D(-0.25f, 0.35f, -1.0f).normalized());
|
||||
m_heatmapProg->setUniformValue("uColorLow", m_colorLow);
|
||||
m_heatmapProg->setUniformValue("uColorMid", m_colorMid);
|
||||
m_heatmapProg->setUniformValue("uColorHigh", m_colorHigh);
|
||||
m_heatmapProg->setUniformValue("uHeightTex", 0);
|
||||
|
||||
glActiveTexture(GL_TEXTURE0);
|
||||
glBindTexture(GL_TEXTURE_2D, m_heightTex);
|
||||
|
||||
glBindVertexArray(m_heatmapVao);
|
||||
glDrawElements(GL_TRIANGLES, m_heatmapIndexCount, GL_UNSIGNED_INT, nullptr);
|
||||
glBindVertexArray(0);
|
||||
|
||||
glBindTexture(GL_TEXTURE_2D, 0);
|
||||
m_heatmapProg->release();
|
||||
}
|
||||
|
||||
void GLWidget::drawToggleButton_(QPainter* painter) {
|
||||
if (!painter) {
|
||||
return;
|
||||
}
|
||||
const QRectF rect(12.0, 12.0, 160.0, 30.0);
|
||||
m_toggleRect = rect;
|
||||
|
||||
painter->save();
|
||||
painter->setRenderHint(QPainter::Antialiasing, true);
|
||||
QColor bg = m_useHeatmap ? QColor(0, 150, 136, 220) : QColor(45, 45, 50, 220);
|
||||
QColor border(255, 255, 255, 80);
|
||||
painter->setPen(QPen(border, 1.0));
|
||||
painter->setBrush(bg);
|
||||
painter->drawRoundedRect(rect, 6.0, 6.0);
|
||||
|
||||
painter->setPen(QColor(255, 255, 255, 235));
|
||||
painter->drawText(rect, Qt::AlignCenter, m_useHeatmap ? QStringLiteral("Heatmap") : QStringLiteral("Dots"));
|
||||
painter->restore();
|
||||
}
|
||||
void GLWidget::initPrograms_() {
|
||||
// Qt Resource 里打包的 shader 文件路径:使用 `:/` 前缀(不是文件系统路径)
|
||||
const QString vsd_path = QStringLiteral(":/shaders/dots.vert");
|
||||
@@ -956,6 +1477,10 @@ void GLWidget::initPrograms_() {
|
||||
const QString fsp_path = QStringLiteral(":/shaders/panel.frag");
|
||||
const QString vsr_path = QStringLiteral(":/shaders/room.vert");
|
||||
const QString fsr_path = QStringLiteral(":/shaders/room.frag");
|
||||
const QString vsh_path = QStringLiteral(":/shaders/heatmap.vert");
|
||||
const QString fsh_path = QStringLiteral(":/shaders/heatmap.frag");
|
||||
const QString vsbase_path = QStringLiteral(":/shaders/base.vert");
|
||||
const QString fsbase_path = QStringLiteral(":/shaders/base.frag");
|
||||
|
||||
{
|
||||
auto *p = new QOpenGLShaderProgram;
|
||||
@@ -1005,6 +1530,30 @@ void GLWidget::initPrograms_() {
|
||||
}
|
||||
m_roomProg = p;
|
||||
}
|
||||
{
|
||||
auto *p = new QOpenGLShaderProgram;
|
||||
const bool okV = p->addShaderFromSourceCode(QOpenGLShader::Vertex, readFile(vsh_path));
|
||||
const bool okF = p->addShaderFromSourceCode(QOpenGLShader::Fragment, readFile(fsh_path));
|
||||
const bool okL = okV && okF && p->link();
|
||||
if (!okL) {
|
||||
qWarning() << "heatmap program build failed:" << vsh_path << fsh_path << p->log();
|
||||
delete p;
|
||||
p = nullptr;
|
||||
}
|
||||
m_heatmapProg = p;
|
||||
}
|
||||
{
|
||||
auto *p = new QOpenGLShaderProgram;
|
||||
const bool okV = p->addShaderFromSourceCode(QOpenGLShader::Vertex, readFile(vsbase_path));
|
||||
const bool okF = p->addShaderFromSourceCode(QOpenGLShader::Fragment, readFile(fsbase_path));
|
||||
const bool okL = okV && okF && p->link();
|
||||
if (!okL) {
|
||||
qWarning() << "base program build failed:" << vsbase_path << fsbase_path << p->log();
|
||||
delete p;
|
||||
p = nullptr;
|
||||
}
|
||||
m_baseProg = p;
|
||||
}
|
||||
}
|
||||
|
||||
void GLWidget::updateInstanceBufferIfNeeded_() {
|
||||
|
||||
@@ -15,9 +15,11 @@
|
||||
#include <QVector3D>
|
||||
#include <QString>
|
||||
#include <QColor>
|
||||
#include <QRectF>
|
||||
#include <atomic>
|
||||
#include <qtmetamacros.h>
|
||||
#include <qvectornd.h>
|
||||
#include <QVector2D>
|
||||
|
||||
struct Ray {
|
||||
QVector3D origin;
|
||||
@@ -101,11 +103,23 @@ private:
|
||||
void initPrograms_();
|
||||
void initDotTexture_();
|
||||
void initRoomGeometry_();
|
||||
void initHeatmapGeometry_();
|
||||
void initBaseGeometry_();
|
||||
void initSkirtGeometry_();
|
||||
void initHeightTexture_();
|
||||
void updateInstanceBufferIfNeeded_();
|
||||
void updateMatrices_();
|
||||
void updateRoom_();
|
||||
void updateHeatmapData_();
|
||||
void uploadHeightTexture_();
|
||||
void updateSkirtVertices_();
|
||||
bool projectToScreen_(const QVector3D& world, QPointF* out) const;
|
||||
int pickDotIndex_(const QPoint& pos, QVector3D* worldOut) const;
|
||||
float sampleHeightValue_(float u, float v) const;
|
||||
void renderHeatmap_();
|
||||
void renderBase_();
|
||||
void renderSkirt_();
|
||||
void drawToggleButton_(QPainter* painter);
|
||||
private:
|
||||
// 传感值范围(用于颜色映射)
|
||||
int m_min = 0;
|
||||
@@ -136,6 +150,8 @@ private:
|
||||
QOpenGLShaderProgram* m_roomProg = nullptr;
|
||||
QOpenGLShaderProgram* m_panelProg = nullptr;
|
||||
QOpenGLShaderProgram* m_dotsProg = nullptr;
|
||||
QOpenGLShaderProgram* m_heatmapProg = nullptr;
|
||||
QOpenGLShaderProgram* m_baseProg = nullptr;
|
||||
|
||||
unsigned int m_dotTex = 0;
|
||||
|
||||
@@ -150,6 +166,39 @@ private:
|
||||
unsigned int m_dotsVao = 0;
|
||||
unsigned int m_dotsVbo = 0;
|
||||
unsigned int m_instanceVbo = 0;
|
||||
|
||||
unsigned int m_heatmapVao = 0;
|
||||
unsigned int m_heatmapVbo = 0;
|
||||
unsigned int m_heatmapIbo = 0;
|
||||
int m_heatmapIndexCount = 0;
|
||||
int m_heatmapCols = 0;
|
||||
int m_heatmapRows = 0;
|
||||
|
||||
unsigned int m_baseVao = 0;
|
||||
unsigned int m_baseVbo = 0;
|
||||
unsigned int m_baseIbo = 0;
|
||||
int m_baseIndexCount = 0;
|
||||
|
||||
unsigned int m_skirtVao = 0;
|
||||
unsigned int m_skirtVbo = 0;
|
||||
unsigned int m_skirtIbo = 0;
|
||||
int m_skirtIndexCount = 0;
|
||||
QVector<QVector2D> m_skirtUVs;
|
||||
QVector<QVector3D> m_skirtNormals;
|
||||
QVector<float> m_skirtVertices;
|
||||
|
||||
unsigned int m_heightTex = 0;
|
||||
int m_heightW = 0;
|
||||
int m_heightH = 0;
|
||||
int m_heightTexW = 0;
|
||||
int m_heightTexH = 0;
|
||||
QVector<float> m_heightValues;
|
||||
bool m_heightDirty = false;
|
||||
|
||||
bool m_useHeatmap = false;
|
||||
QRectF m_toggleRect;
|
||||
int m_upscale = 100;
|
||||
float m_dentMax = 0.08f;
|
||||
|
||||
unsigned int room_vao = 0;
|
||||
unsigned int room_vbo = 0;
|
||||
|
||||
Reference in New Issue
Block a user