diff --git a/CMakeLists.txt b/CMakeLists.txt
index 8c0786e..266dda4 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -31,7 +31,7 @@ include_directories(${CMAKE_CURRENT_SOURCE_DIR}/src)
qt_standard_project_setup()
-add_executable(TactileIpc3D
+add_executable(TactileIpc3D WIN32
main.cpp
resources.qrc
src/translation_manager.h
@@ -57,8 +57,6 @@ add_executable(TactileIpc3D
src/serial/serial_qt_transport.h
src/serial/serial_qt_transport.cpp
src/serial/serial_types.h
- src/serial/piezoresistive_a_protocol.h
- src/serial/piezoresistive_a_protocol.cpp
src/ringbuffer.h
src/ringbuffer.cpp
src/sparkline_plotitem.h
@@ -79,6 +77,19 @@ target_link_libraries(TactileIpc3D PRIVATE
Qt6::QuickDialogs2
QXlsx::QXlsx
)
+
+add_library(PiezoresistiveAPlugin SHARED
+ plugins/piezoresistive_a/piezoresistive_a_plugin.h
+ plugins/piezoresistive_a/piezoresistive_a_plugin.cpp
+ src/serial/piezoresistive_a_protocol.h
+ src/serial/piezoresistive_a_protocol.cpp
+)
+target_include_directories(PiezoresistiveAPlugin PRIVATE
+ ${CMAKE_CURRENT_SOURCE_DIR}/src
+)
+target_link_libraries(PiezoresistiveAPlugin PRIVATE
+ Qt6::Core
+)
target_include_directories(TactileIpc3D PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/3rdpart/OpenCV/include
)
@@ -132,8 +143,25 @@ set_target_properties(TactileIpc3D PROPERTIES
RUNTIME_OUTPUT_DIRECTORY_MINSIZEREL "${runtime_out_dir}/MinSizeRel"
)
+set_target_properties(PiezoresistiveAPlugin PROPERTIES
+ RUNTIME_OUTPUT_DIRECTORY "${runtime_out_dir}/plugins/decoders"
+ RUNTIME_OUTPUT_DIRECTORY_DEBUG "${runtime_out_dir}/Debug/plugins/decoders"
+ RUNTIME_OUTPUT_DIRECTORY_RELEASE "${runtime_out_dir}/Release/plugins/decoders"
+ RUNTIME_OUTPUT_DIRECTORY_RELWITHDEBINFO "${runtime_out_dir}/RelWithDebInfo/plugins/decoders"
+ RUNTIME_OUTPUT_DIRECTORY_MINSIZEREL "${runtime_out_dir}/MinSizeRel/plugins/decoders"
+ LIBRARY_OUTPUT_DIRECTORY "${runtime_out_dir}/plugins/decoders"
+ LIBRARY_OUTPUT_DIRECTORY_DEBUG "${runtime_out_dir}/Debug/plugins/decoders"
+ LIBRARY_OUTPUT_DIRECTORY_RELEASE "${runtime_out_dir}/Release/plugins/decoders"
+ LIBRARY_OUTPUT_DIRECTORY_RELWITHDEBINFO "${runtime_out_dir}/RelWithDebInfo/plugins/decoders"
+ LIBRARY_OUTPUT_DIRECTORY_MINSIZEREL "${runtime_out_dir}/MinSizeRel/plugins/decoders"
+)
+
include(GNUInstallDirs)
install(TARGETS TactileIpc3D
RUNTIME DESTINATION bin
)
+install(TARGETS PiezoresistiveAPlugin
+ RUNTIME DESTINATION bin/plugins/decoders
+ LIBRARY DESTINATION bin/plugins/decoders
+)
diff --git a/Configure b/Configure
deleted file mode 100644
index f1c8738..0000000
--- a/Configure
+++ /dev/null
@@ -1 +0,0 @@
-==
diff --git a/VERSION.txt b/VERSION.txt
index 3eefcb9..1d0ba9e 100644
--- a/VERSION.txt
+++ b/VERSION.txt
@@ -1 +1 @@
-1.0.0
+0.4.0
diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md
index 980b2c6..86cfc74 100644
--- a/docs/ARCHITECTURE.md
+++ b/docs/ARCHITECTURE.md
@@ -461,6 +461,44 @@ classDiagram
- `m_protocols`:协议字典。
- `m_activeName`:当前协议名。
+### 协议插件化(`plugins/decoders`)
+- 目标:每个协议作为独立 DLL 插件,可按需安装与动态加载。
+- 关键接口:
+ - `IProtocolPlugin`:暴露 `protocolName()` / `apiVersion()` / `createBundle()`。
+ - `ProtocolBundle`:`codec/decoder/format` 三件套。
+- 加载流程:
+ - `SerialBackend` 启动时调用 `SerialManager::loadPlugins()`。
+ - `SerialManager` 扫描 `appDir/plugins/decoders` 目录,按平台过滤后缀(Windows: `.dll`)。
+ - 校验 `apiVersion`,创建并注册协议 bundle。
+ - 通过 `m_pluginLoaders` 保持插件常驻,避免对象失效。
+
+#### 插件 UML (Mermaid)
+```mermaid
+classDiagram
+ class IProtocolPlugin {
+ +protocolName()
+ +apiVersion()
+ +createBundle()
+ }
+ class ProtocolBundle {
+ +codec
+ +decoder
+ +format
+ }
+ class SerialManager {
+ +loadPlugins(dir)
+ +registerProtocol()
+ +setActiveProtocol()
+ }
+ class SerialBackend {
+ +initPlugins()
+ }
+
+ SerialBackend --> SerialManager
+ SerialManager --> IProtocolPlugin
+ IProtocolPlugin --> ProtocolBundle
+```
+
### ISerialTransport (`src/serial/serial_transport.h`)
- 作用:串口传输抽象层,屏蔽不同平台差异。
- 接口:
@@ -633,6 +671,7 @@ classDiagram
## 更新记录
+- 2026-01-20:协议解码插件化(DLL),SerialBackend 启动时动态加载 `plugins/decoders`,安装器支持按组件选择协议插件。
- 2026-01-11:新增 `QtSerialTransport`(`QSerialPort` 传输实现)并设为默认传输;补齐点选拾取逻辑;新增数据流/时序图并补充可视化 TODO 说明。
- 2026-01-12:`LeftPanel` 新增颜色映射参数;`AppBackend`/`GLWidget` 增加颜色与范围接口,shader 使用三色渐变映射数据值。
- 2026-01-11:补充串口配置流程图与配置接口说明(协议/参数/解码器绑定/打开流程)。
diff --git a/i18n/app_en_US.ts b/i18n/app_en_US.ts
index 958f138..245ef6c 100644
--- a/i18n/app_en_US.ts
+++ b/i18n/app_en_US.ts
@@ -184,6 +184,10 @@
显示网络
Show Grid
+
+ 凹陷表面
+ Dented Surface
+
显示坐标轴
Show Axes
@@ -208,7 +212,15 @@
选择高色
Select High Color
-
+
+ 零色
+ Zero Color
+
+
+ 选择零色
+ Select Zero Color
+
+
OpenFileDialog
diff --git a/i18n/app_zh_CN.ts b/i18n/app_zh_CN.ts
index d783a0a..555ea93 100644
--- a/i18n/app_zh_CN.ts
+++ b/i18n/app_zh_CN.ts
@@ -148,6 +148,10 @@
显示网络
显示网络
+
+ 凹陷表面
+ 凹陷表面
+
显示坐标轴
显示坐标轴
@@ -160,7 +164,15 @@
导出数据
导出数据
-
+
+ 零色
+ 零色
+
+
+ 选择零色
+ 选择零色
+
+
RightPanel
diff --git a/installer/TactileIpc3D.iss b/installer/TactileIpc3D.iss
index ddc08df..13499c2 100644
--- a/installer/TactileIpc3D.iss
+++ b/installer/TactileIpc3D.iss
@@ -1,5 +1,5 @@
#define MyAppName "TactileIpc3D"
-#define MyAppVersion "0.2.0"
+#define MyAppVersion "0.4.0"
#define MyAppPublisher "TactileIpc3D"
#define MyAppExeName "TactileIpc3D.exe"
#define SourceDir "..\\build\Desktop_Qt_6_8_3_MinGW_64_bit-Release\\out\Release"
@@ -41,8 +41,13 @@ Name: "english"; MessagesFile: "compiler:Default.isl"
[Tasks]
Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: unchecked
+[Components]
+Name: "core"; Description: "Core Application"; Types: full compact custom; Flags: fixed
+Name: "decoder_pzr_a"; Description: "Piezoresistive A Decoder (PZR-A)"; Types: full
+
[Files]
-Source: "{#SourceDir}\\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs
+Source: "{#SourceDir}\\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs; Components: core; Excludes: "plugins\\decoders\\*.dll"
+Source: "{#SourceDir}\\plugins\\decoders\\libPiezoresistiveAPlugin.dll"; DestDir: "{app}\\plugins\\decoders"; Flags: ignoreversion; Components: decoder_pzr_a
[Icons]
Name: "{group}\\{#MyAppName}"; Filename: "{app}\\{#MyAppExeName}"
diff --git a/main.cpp b/main.cpp
index e07d938..11dfa7d 100644
--- a/main.cpp
+++ b/main.cpp
@@ -116,9 +116,11 @@ int main(int argc, char *argv[]) {
glw->setSpec(12, 7, 0.1f, 0.03f);
glw->setPanelThickness(0.08f);
glw->setRange(backend.rangeMin(), backend.rangeMax());
+ glw->setColorZero(backend.colorZero());
glw->setColorLow(backend.colorLow());
glw->setColorMid(backend.colorMid());
glw->setColorHigh(backend.colorHigh());
+ glw->setUseHeatmap(backend.useHeatmap());
/* backend.data()->setLiveRenderCallback([glw](const DataFrame& frame) {
if (frame.data.size() != glw->dotCount())
@@ -177,12 +179,16 @@ int main(int argc, char *argv[]) {
QObject::connect(&backend, &AppBackend::showGridChanged, glw,
&GLWidget::setShowGrid);
+ QObject::connect(&backend, &AppBackend::useHeatmapChanged, glw,
+ &GLWidget::setUseHeatmap);
QObject::connect(&backend, &AppBackend::sensorRowChanged, glw,
&GLWidget::setRow);
QObject::connect(&backend, &AppBackend::sensorColChanged, glw,
&GLWidget::setCol);
QObject::connect(&backend, &AppBackend::rangeChanged, glw,
&GLWidget::setRange);
+ QObject::connect(&backend, &AppBackend::colorZeroChanged, glw,
+ &GLWidget::setColorZero);
QObject::connect(&backend, &AppBackend::colorLowChanged, glw,
&GLWidget::setColorLow);
QObject::connect(&backend, &AppBackend::colorMidChanged, glw,
diff --git a/qml/content/ColorPickerDialog.qml b/qml/content/ColorPickerDialog.qml
index 6cf8763..044dba5 100644
--- a/qml/content/ColorPickerDialog.qml
+++ b/qml/content/ColorPickerDialog.qml
@@ -503,11 +503,20 @@ Window {
border.color: root.controlBorder
}
+ function applyIfValid() {
+ const c = root.parseHex(text)
+ if (c) root.syncFromColor(c)
+ }
+ onTextEdited: applyIfValid()
onEditingFinished: {
const c = root.parseHex(text)
if (c) root.syncFromColor(c)
else text = root.colorToHexAARRGGBB(root.color)
}
+ onAccepted: {
+ applyIfValid()
+ focus = false
+ }
}
diff --git a/qml/content/LeftPanel.qml b/qml/content/LeftPanel.qml
index e7d9b46..c90a3b4 100644
--- a/qml/content/LeftPanel.qml
+++ b/qml/content/LeftPanel.qml
@@ -63,6 +63,7 @@ Rectangle {
RowLayout {
Layout.fillWidth: true
+ Layout.topMargin: 4
spacing: 8
Label {
@@ -167,6 +168,7 @@ Rectangle {
text: root.formatHexByte(Backend.serial.deviceAddress)
placeholderText: "0x01"
inputMethodHints: Qt.ImhPreferUppercase
+ property bool _internalUpdate: false
validator: RegularExpressionValidator {
regularExpression: /^(0x|0X)?[0-9a-fA-F]{1,2}$/
}
@@ -183,7 +185,9 @@ Rectangle {
Connections {
target: Backend.serial
function onDeviceAddressChanged() {
+ addrField._internalUpdate = true
addrField.text = root.formatHexByte(Backend.serial.deviceAddress)
+ addrField._internalUpdate = false
}
}
}
@@ -423,12 +427,31 @@ Rectangle {
color: root.textColor
}
SpinBox {
+ id: rangeMinBox
Layout.fillWidth: true
from: -999999
to: 999999
editable: true
value: Backend.rangeMin
+ function applyTextEdit() {
+ if (!contentItem) return
+ const parsed = valueFromText(contentItem.text, locale)
+ if (isNaN(parsed)) return
+ const clamped = Math.max(from, Math.min(to, parsed))
+ if (Backend.rangeMin !== clamped) {
+ Backend.rangeMin = clamped
+ }
+ }
onValueModified: Backend.rangeMin = value
+ Connections {
+ target: rangeMinBox.contentItem
+ function onTextEdited() { rangeMinBox.applyTextEdit() }
+ function onEditingFinished() { rangeMinBox.applyTextEdit() }
+ function onAccepted() {
+ rangeMinBox.applyTextEdit()
+ rangeMinBox.focus = false
+ }
+ }
}
}
@@ -441,12 +464,62 @@ Rectangle {
color: root.textColor
}
SpinBox {
+ id: rangeMaxBox
Layout.fillWidth: true
from: -999999
to: 999999
editable: true
value: Backend.rangeMax
+ function applyTextEdit() {
+ if (!contentItem) return
+ const parsed = valueFromText(contentItem.text, locale)
+ if (isNaN(parsed)) return
+ const clamped = Math.max(from, Math.min(to, parsed))
+ if (Backend.rangeMax !== clamped) {
+ Backend.rangeMax = clamped
+ }
+ }
onValueModified: Backend.rangeMax = value
+ Connections {
+ target: rangeMaxBox.contentItem
+ function onTextEdited() { rangeMaxBox.applyTextEdit() }
+ function onEditingFinished() { rangeMaxBox.applyTextEdit() }
+ function onAccepted() {
+ rangeMaxBox.applyTextEdit()
+ rangeMaxBox.focus = false
+ }
+ }
+ }
+ }
+ RowLayout {
+ Layout.fillWidth: true
+ spacing: 8
+ Label {
+ text: root.tr("零色")
+ Layout.preferredWidth: 90
+ color: root.textColor
+ }
+ Rectangle {
+ width: 22
+ height: 22
+ radius: 4
+ color: Backend.colorZero
+ border.width: 1
+ border.color: Qt.rgba(0, 0, 0, 0.2)
+ Layout.alignment: Qt.AlignVCenter
+ MouseArea {
+ anchors.fill: parent
+ onClicked: {
+ zeroColorDialog.openWith(Backend.colorZero)
+ }
+ }
+ }
+ Button {
+ text: root.tr("选择")
+ Layout.fillWidth: true
+ onClicked: {
+ zeroColorDialog.openWith(Backend.colorZero)
+ }
}
}
@@ -557,6 +630,11 @@ Rectangle {
checked: Backend.showGrid
onToggled: Backend.showGrid = checked
}
+ CheckBox {
+ text: root.tr("凹陷表面")
+ checked: Backend.useHeatmap
+ onToggled: Backend.useHeatmap = checked
+ }
CheckBox {
text: root.tr("显示坐标轴")
checked: false
@@ -589,6 +667,13 @@ Rectangle {
Item { Layout.fillHeight: true }
}
}
+
+ ColorPickerDialog {
+ id: zeroColorDialog
+ title: root.tr("选择零色")
+ onAccepted: Backend.colorZero = c
+ }
+
ColorPickerDialog {
id: lowColorDialog
diff --git a/shaders/base.frag b/shaders/base.frag
index 1ecd2ea..57b5f72 100644
--- a/shaders/base.frag
+++ b/shaders/base.frag
@@ -2,14 +2,51 @@
in vec3 vNormal;
in vec3 vWorldPos;
+in vec2 vUV;
out vec4 FragColor;
uniform vec3 uCameraPos;
uniform vec3 uLightDir;
-uniform vec3 uColor;
+uniform sampler2D uHeightTex;
+uniform float uMinV;
+uniform float uMaxV;
+uniform vec2 uTexelSize;
+uniform vec3 uColorZero;
+uniform vec3 uColorLow;
+uniform vec3 uColorMid;
+uniform vec3 uColorHigh;
float saturate(float x) { return clamp(x, 0.0, 1.0); }
+float value01(float v) {
+ return saturate((v - uMinV) / max(uMaxV - uMinV, 1e-6));
+}
+
+vec3 colorRamp(float t) {
+ if (t < 0.5) {
+ return mix(uColorLow, uColorMid, t / 0.5);
+ }
+ return mix(uColorMid, uColorHigh, (t - 0.5) / 0.5);
+}
+
+float maxNeighborValue(vec2 uv) {
+ vec2 texSizeF = 1.0 / uTexelSize;
+ ivec2 texSizeI = ivec2(max(vec2(1.0), floor(texSizeF + 0.5)));
+ ivec2 maxCoord = texSizeI - ivec2(1);
+
+ vec2 texelF = clamp(uv * texSizeF, vec2(0.0), vec2(maxCoord));
+ ivec2 base = ivec2(floor(texelF));
+ ivec2 base10 = min(base + ivec2(1, 0), maxCoord);
+ ivec2 base01 = min(base + ivec2(0, 1), maxCoord);
+ ivec2 base11 = min(base + ivec2(1, 1), maxCoord);
+
+ float v00 = texelFetch(uHeightTex, base, 0).r;
+ float v10 = texelFetch(uHeightTex, base10, 0).r;
+ float v01 = texelFetch(uHeightTex, base01, 0).r;
+ float v11 = texelFetch(uHeightTex, base11, 0).r;
+ return max(max(v00, v10), max(v01, v11));
+}
+
void main() {
vec3 N = normalize(vNormal);
vec3 L = normalize(uLightDir);
@@ -19,7 +56,16 @@ void main() {
float diff = saturate(dot(N, L));
float spec = pow(saturate(dot(N, H)), 24.0);
- vec3 col = uColor * (0.35 + 0.65 * diff);
+ float vC = texture(uHeightTex, vUV).r;
+ bool isZero = abs(vC) <= 1e-6;
+ float t = value01(vC);
+ float m = maxNeighborValue(vUV);
+ float eps = max(1e-4, 0.001 * (uMaxV - uMinV));
+ float force = smoothstep(uMaxV - eps, uMaxV, m);
+ t = max(t, force);
+ vec3 base = isZero ? uColorZero : colorRamp(t);
+
+ vec3 col = base * (0.35 + 0.65 * diff);
col += vec3(1.0) * spec * 0.10;
FragColor = vec4(clamp(col, 0.0, 1.0), 1.0);
}
diff --git a/shaders/base.vert b/shaders/base.vert
index 6ebd6c2..a0e7b21 100644
--- a/shaders/base.vert
+++ b/shaders/base.vert
@@ -2,14 +2,17 @@
layout(location = 0) in vec3 aPos;
layout(location = 1) in vec3 aNormal;
+layout(location = 2) in vec2 aUV;
out vec3 vNormal;
out vec3 vWorldPos;
+out vec2 vUV;
uniform mat4 uMVP;
void main() {
vNormal = aNormal;
vWorldPos = aPos;
+ vUV = aUV;
gl_Position = uMVP * vec4(aPos, 1.0);
}
diff --git a/shaders/dots.frag b/shaders/dots.frag
index 6cdaaed..0e39f42 100644
--- a/shaders/dots.frag
+++ b/shaders/dots.frag
@@ -6,6 +6,7 @@ out vec4 FragColor;
uniform float uMinV;
uniform float uMaxV;
+uniform vec3 uColorZero;
uniform vec3 uColorLow;
uniform vec3 uColorMid;
uniform vec3 uColorHigh;
@@ -112,7 +113,8 @@ void main() {
// data color directly (no remaining gold tint), while preserving depth cues.
vec3 metalBase = vec3(0.98, 0.82, 0.30);
float value01 = clamp((vValue - uMinV) / max(1e-6, (uMaxV - uMinV)), 0.0, 1.0);
- vec3 dataCol = dataColorRamp(value01);
+ bool isZero = abs(vValue) <= 1e-6;
+ vec3 dataCol = isZero ? uColorZero : dataColorRamp(value01);
bool hasData = (uHasData != 0);
vec3 baseColor = hasData ? dataCol : metalBase;
diff --git a/shaders/heatmap.frag b/shaders/heatmap.frag
index 2205794..af4a260 100644
--- a/shaders/heatmap.frag
+++ b/shaders/heatmap.frag
@@ -12,6 +12,7 @@ uniform vec2 uTexelSize;
uniform vec2 uPlaneSize;
uniform vec3 uCameraPos;
uniform vec3 uLightDir;
+uniform vec3 uColorZero;
uniform vec3 uColorLow;
uniform vec3 uColorMid;
uniform vec3 uColorHigh;
@@ -22,6 +23,24 @@ float value01(float v) {
return saturate((v - uMinV) / max(uMaxV - uMinV, 1e-6));
}
+float maxNeighborValue(vec2 uv) {
+ vec2 texSizeF = 1.0 / uTexelSize;
+ ivec2 texSizeI = ivec2(max(vec2(1.0), floor(texSizeF + 0.5)));
+ ivec2 maxCoord = texSizeI - ivec2(1);
+
+ vec2 texelF = clamp(uv * texSizeF, vec2(0.0), vec2(maxCoord));
+ ivec2 base = ivec2(floor(texelF));
+ ivec2 base10 = min(base + ivec2(1, 0), maxCoord);
+ ivec2 base01 = min(base + ivec2(0, 1), maxCoord);
+ ivec2 base11 = min(base + ivec2(1, 1), maxCoord);
+
+ float v00 = texelFetch(uHeightTex, base, 0).r;
+ float v10 = texelFetch(uHeightTex, base10, 0).r;
+ float v01 = texelFetch(uHeightTex, base01, 0).r;
+ float v11 = texelFetch(uHeightTex, base11, 0).r;
+ return max(max(v00, v10), max(v01, v11));
+}
+
vec3 colorRamp(float t) {
if (t < 0.5) {
return mix(uColorLow, uColorMid, t / 0.5);
@@ -31,8 +50,13 @@ vec3 colorRamp(float t) {
void main() {
float vC = texture(uHeightTex, vUV).r;
+ bool isZero = abs(vC) <= 1e-6;
float t = value01(vC);
- vec3 base = colorRamp(t);
+ float m = maxNeighborValue(vUV);
+ float eps = max(1e-4, 0.001 * (uMaxV - uMinV));
+ float force = smoothstep(uMaxV - eps, uMaxV, m);
+ t = max(t, force);
+ vec3 base = isZero ? uColorZero : colorRamp(t);
float vL = texture(uHeightTex, vUV - vec2(uTexelSize.x, 0.0)).r;
float vR = texture(uHeightTex, vUV + vec2(uTexelSize.x, 0.0)).r;
diff --git a/src/backend.cpp b/src/backend.cpp
index b8e4073..573d1cf 100644
--- a/src/backend.cpp
+++ b/src/backend.cpp
@@ -5,6 +5,7 @@
#include "backend.h"
#include "data_backend.h"
#include "serial/serial_backend.h"
+#include
#include
AppBackend::AppBackend(QObject* parent)
@@ -51,6 +52,13 @@ void AppBackend::setShowGrid(bool on) {
emit showGridChanged(on);
}
+void AppBackend::setUseHeatmap(bool on) {
+ if (m_useHeatmap == on)
+ return;
+ m_useHeatmap = on;
+ emit useHeatmapChanged(on);
+}
+
void AppBackend::setSensorCol(int c) {
if (m_serial->connected()) {
return;
@@ -93,6 +101,13 @@ void AppBackend::setRangeMax(int v) {
emit rangeChanged(m_rangeMin, m_rangeMax);
}
+void AppBackend::setColorZero(const QColor& color) {
+ if (m_colorZero == color)
+ return;
+ m_colorZero = color;
+ emit colorZeroChanged(m_colorZero);
+}
+
void AppBackend::setColorLow(const QColor& color) {
if (m_colorLow == color)
return;
diff --git a/src/backend.h b/src/backend.h
index 828b505..81da272 100644
--- a/src/backend.h
+++ b/src/backend.h
@@ -7,6 +7,7 @@
#include
#include
#include
+#include
#include
#include "data_backend.h"
@@ -18,12 +19,14 @@ class AppBackend : public QObject {
Q_PROPERTY(QString language READ language WRITE setLanguage NOTIFY languageChanged)
Q_PROPERTY(bool connected READ connected NOTIFY connectedChanged)
Q_PROPERTY(bool showGrid READ showGrid WRITE setShowGrid NOTIFY showGridChanged);
+ Q_PROPERTY(bool useHeatmap READ useHeatmap WRITE setUseHeatmap NOTIFY useHeatmapChanged);
Q_PROPERTY(SerialBackend* serial READ serial CONSTANT)
Q_PROPERTY(DataBackend* data READ data CONSTANT)
Q_PROPERTY(int sensorCol READ sensorCol WRITE setSensorCol NOTIFY sensorColChanged);
Q_PROPERTY(int sensorRow READ sensorRow WRITE setSensorRow NOTIFY sensorRowChanged);
Q_PROPERTY(int rangeMin READ rangeMin WRITE setRangeMin NOTIFY rangeMinChanged);
Q_PROPERTY(int rangeMax READ rangeMax WRITE setRangeMax NOTIFY rangeMaxChanged);
+ Q_PROPERTY(QColor colorZero READ colorZero WRITE setColorZero NOTIFY colorZeroChanged);
Q_PROPERTY(QColor colorLow READ colorLow WRITE setColorLow NOTIFY colorLowChanged);
Q_PROPERTY(QColor colorMid READ colorMid WRITE setColorMid NOTIFY colorMidChanged);
Q_PROPERTY(QColor colorHigh READ colorHigh WRITE setColorHigh NOTIFY colorHighChanged);
@@ -43,6 +46,8 @@ public:
bool showGrid() const { return m_showGrid; }
void setShowGrid(bool on);
+ bool useHeatmap() const { return m_useHeatmap; }
+ void setUseHeatmap(bool on);
int sensorCol() const { qInfo() << "col:" << m_sensorCol; return m_sensorCol; }
int sensorRow() const { qInfo() << "row:" << m_sensorRow; return m_sensorRow; }
void setSensorRow(int r);
@@ -51,9 +56,11 @@ public:
int rangeMax() const { return m_rangeMax; }
void setRangeMin(int v);
void setRangeMax(int v);
+ QColor colorZero() const { return m_colorZero; }
QColor colorLow() const { return m_colorLow; }
QColor colorMid() const { return m_colorMid; }
QColor colorHigh() const { return m_colorHigh; }
+ void setColorZero(const QColor& color);
void setColorLow(const QColor& color);
void setColorMid(const QColor& color);
void setColorHigh(const QColor& color);
@@ -63,11 +70,13 @@ signals:
void languageChanged();
void connectedChanged();
void showGridChanged(bool on);
+ void useHeatmapChanged(bool on);
void sensorColChanged(int c);
void sensorRowChanged(int r);
void rangeMinChanged(int v);
void rangeMaxChanged(int v);
void rangeChanged(int minV, int maxV);
+ void colorZeroChanged(const QColor& color);
void colorLowChanged(const QColor& color);
void colorMidChanged(const QColor& color);
void colorHighChanged(const QColor& color);
@@ -78,10 +87,12 @@ private:
QString m_language = QStringLiteral("zh_CN");
bool m_showGrid = true;
+ bool m_useHeatmap = false;
int m_sensorRow = 12;
int m_sensorCol = 7;
int m_rangeMin = 0;
int m_rangeMax = 1000;
+ QColor m_colorZero = QColor::fromRgbF(0.10, 0.75, 1.00);
QColor m_colorLow = QColor::fromRgbF(0.10, 0.75, 1.00);
QColor m_colorMid = QColor::fromRgbF(0.10, 0.95, 0.35);
QColor m_colorHigh = QColor::fromRgbF(1.00, 0.22, 0.10);
diff --git a/src/data_backend.cpp b/src/data_backend.cpp
index a7b5766..4dce6ef 100644
--- a/src/data_backend.cpp
+++ b/src/data_backend.cpp
@@ -127,6 +127,8 @@ bool DataBackend::importXlsx(const QString& path) {
cells[r][c] = v;
}
}
+
+ return true;
}
void DataBackend::startPlayback(int intervalMs) {
diff --git a/src/globalhelper.h b/src/globalhelper.h
index 79c63a9..6266c24 100644
--- a/src/globalhelper.h
+++ b/src/globalhelper.h
@@ -5,48 +5,217 @@
#ifndef TACTILEIPC3D_GLOBALHELPER_H
#define TACTILEIPC3D_GLOBALHELPER_H
+#include
#include
#include
-#include
+#include
+#include
+#include
+#include
+
+const QString APP_VERSION = "0.4.0";
class GlobalHelper {
-public:
const std::string fuck = "fuck you lsp!!!";
- static GlobalHelper* Instance() {
+
+public :
+ static GlobalHelper *Instance() {
static GlobalHelper ins;
return &ins;
}
- static void transToMultiMatrix(const cv::Mat& raw_data, float min_threshold,
- float max_range, cv::Size display_res, cv::Mat& out_matrix) {
+ static void transToMultiMatrix(const cv::Mat &raw_data, float min_threshold,
+ float max_range, cv::Size display_res,
+ cv::Mat &out_matrix) {
CV_Assert(raw_data.type() == CV_32F);
+ const float safe_max = std::max(max_range, 1e-6f);
+ const float safe_span = std::max(safe_max - min_threshold, 1e-6f);
+
+ cv::Mat saturate_mask_u8;
+ cv::compare(raw_data, max_range, saturate_mask_u8, cv::CMP_GE);
+ cv::Mat zero_mask_u8;
+ cv::compare(raw_data, 0.0f, zero_mask_u8, cv::CMP_LE);
+
cv::Mat raw = raw_data.clone();
- raw.setTo(0.0f, raw < min_threshold);
+ cv::min(raw, safe_max, raw);
- double maxVal = 0.0;
- cv::minMaxLoc(raw, nullptr, &maxVal);
- if (maxVal > 0.0) {
- cv::GaussianBlur(raw, raw, cv::Size(3, 3), 0.8, 0.8, cv::BORDER_DEFAULT);
- }
+ cv::Mat peak_norm = (raw - min_threshold) / safe_span;
+ cv::max(peak_norm, 0.0f, peak_norm);
+ cv::min(peak_norm, 1.0f, peak_norm);
- 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::medianBlur(raw, raw, 3);
+ cv::GaussianBlur(raw, raw, cv::Size(5, 5), 1.2, 1.2, cv::BORDER_DEFAULT);
+
+ cv::Mat norm_data = (raw - min_threshold) / safe_span;
cv::max(norm_data, 0.0f, norm_data);
+ cv::min(norm_data, 1.0f, norm_data);
- cv::pow(norm_data, 0.7, norm_data);
+ cv::pow(norm_data, 1.4, norm_data);
cv::Mat smoothed;
cv::resize(norm_data, smoothed, display_res, 0.0, 0.0, cv::INTER_CUBIC);
+ cv::max(smoothed, 0.0f, smoothed);
+ cv::min(smoothed, 1.0f, smoothed);
- cv::GaussianBlur(smoothed, smoothed, cv::Size(31, 31), 0.0, 0.0, cv::BORDER_DEFAULT);
+ const int min_dim =
+ std::max(1, std::min(display_res.width, display_res.height));
+ const double sigma = std::max(1.0, 0.02 * min_dim);
+ int ksize = std::max(3, (static_cast(std::ceil(sigma * 6)) | 1));
+ cv::GaussianBlur(smoothed, smoothed, cv::Size(ksize, ksize), sigma, sigma,
+ cv::BORDER_DEFAULT);
+ cv::max(smoothed, 0.0f, smoothed);
+ cv::min(smoothed, 1.0f, smoothed);
- smoothed *= safe_range;
+ cv::Mat peak_resized;
+ cv::resize(peak_norm, peak_resized, display_res, 0.0, 0.0,
+ cv::INTER_CUBIC);
+ cv::max(smoothed, peak_resized, smoothed);
+
+ if (!saturate_mask_u8.empty()) {
+ cv::Mat mask_f;
+ saturate_mask_u8.convertTo(mask_f, CV_32F, 1.0 / 255.0);
+ cv::Mat mask_resized;
+ cv::resize(mask_f, mask_resized, display_res, 0.0, 0.0,
+ cv::INTER_CUBIC);
+ // Soften the threshold to avoid blocky plateaus.
+ const double mask_sigma = std::max(0.5, 0.008 * min_dim);
+ int mask_ksize =
+ std::max(3, (static_cast(std::ceil(mask_sigma * 6)) | 1));
+ cv::GaussianBlur(mask_resized, mask_resized,
+ cv::Size(mask_ksize, mask_ksize), mask_sigma,
+ mask_sigma, cv::BORDER_DEFAULT);
+ cv::max(mask_resized, 0.0f, mask_resized);
+ cv::min(mask_resized, 1.0f, mask_resized);
+ smoothed = cv::max(smoothed, mask_resized);
+ }
+
+ if (!zero_mask_u8.empty()) {
+ cv::Mat zero_f;
+ zero_mask_u8.convertTo(zero_f, CV_32F, 1.0 / 255.0);
+ cv::Mat zero_resized;
+ cv::resize(zero_f, zero_resized, display_res, 0.0, 0.0,
+ cv::INTER_CUBIC);
+ const double zero_sigma = std::max(0.5, 0.006 * min_dim);
+ int zero_ksize =
+ std::max(3, (static_cast(std::ceil(zero_sigma * 6)) | 1));
+ cv::GaussianBlur(zero_resized, zero_resized,
+ cv::Size(zero_ksize, zero_ksize), zero_sigma,
+ zero_sigma, cv::BORDER_DEFAULT);
+ cv::max(zero_resized, 0.0f, zero_resized);
+ cv::min(zero_resized, 1.0f, zero_resized);
+ cv::Mat zero_bin;
+ cv::threshold(zero_resized, zero_bin, 0.5, 255.0,
+ cv::THRESH_BINARY);
+ zero_bin.convertTo(zero_bin, CV_8U);
+ smoothed.setTo(0.0f, zero_bin);
+ }
+
+ smoothed *= safe_max;
out_matrix = smoothed;
}
+
+ static void SaveGridConfig(bool b) {
+ QSettings settings(GlobalHelper::GetConfigFilePath(), QSettings::IniFormat);
+ settings.setValue("display/grid", b);
+ }
+
+ static void SaveAxisConfig(bool b) {
+ QSettings settings(GlobalHelper::GetConfigFilePath(), QSettings::IniFormat);
+ settings.setValue("display/axis", b);
+ }
+
+ static void SaveSurfaceConfig(bool b) {
+ QSettings settings(GlobalHelper::GetConfigFilePath(), QSettings::IniFormat);
+ settings.setValue("display/surface", b);
+ }
+
+ static void GetGridConfig(bool& b) {
+ QSettings settings(GlobalHelper::GetConfigFilePath(), QSettings::IniFormat);
+ b = settings.value("display/grid", b).toBool();
+ }
+
+ static void GetAxisConfig(bool& b) {
+ QSettings settings(GlobalHelper::GetConfigFilePath(), QSettings::IniFormat);
+ b = settings.value("display/axis", b).toBool();
+ }
+
+ static void GetSurfaceConfig(bool b) {
+ QSettings settings(GlobalHelper::GetConfigFilePath(), QSettings::IniFormat);
+ b = settings.value("display/surface", b).toBool();
+ }
+
+ static void SaveLastRow(int value) {
+ QSettings settings(GlobalHelper::GetConfigFilePath(), QSettings::IniFormat);
+ settings.setValue("spec/row", value);
+ }
+
+ static void GetLastRow(int& value) {
+ QSettings settings(GlobalHelper::GetConfigFilePath(), QSettings::IniFormat);
+ value = settings.value("spec/row", value).toInt();
+ }
+
+ static void GetLastCol(int value) {
+ QSettings settings(GlobalHelper::GetConfigFilePath(), QSettings::IniFormat);
+ settings.setValue("spec/col", value);
+ }
+
+ static void GetLastCol(int& value) {
+ QSettings settings(GlobalHelper::GetConfigFilePath(), QSettings::IniFormat);
+ value = settings.value("spec/col", value).toInt();
+ }
+
+ static void SaveZeroColor(QString color) {
+ QSettings settings(GlobalHelper::GetConfigFilePath(), QSettings::IniFormat);
+ settings.setValue("color/zero", color);
+ }
+
+ static void GetZeroColor(QString& color) {
+ QSettings settings(GlobalHelper::GetConfigFilePath(), QSettings::IniFormat);
+ color = settings.value("color/zero", color).toString();
+ }
+
+ static void SaveLowColor(QString color) {
+ QSettings settings(GlobalHelper::GetConfigFilePath(), QSettings::IniFormat);
+ settings.setValue("color/low", color);
+ }
+
+ static void GetLowColor(QString& color) {
+ QSettings settings(GlobalHelper::GetConfigFilePath(), QSettings::IniFormat);
+ color = settings.value("color/low", color).toString();
+ }
+
+ static void SaveMidColor(QString color) {
+ QSettings settings(GlobalHelper::GetConfigFilePath(), QSettings::IniFormat);
+ settings.setValue("color/mid", color);
+ }
+
+ static void GetMidColor(QString& color) {
+ QSettings settings(GlobalHelper::GetConfigFilePath(), QSettings::IniFormat);
+ color = settings.value("color/mid", color).toString();
+ }
+
+ static void SaveHighColor(QString color) {
+ QSettings settings(GlobalHelper::GetConfigFilePath(), QSettings::IniFormat);
+ settings.setValue("color/high", color);
+ }
+
+ static void GetHighColor(QString& color) {
+ QSettings settings(GlobalHelper::GetConfigFilePath(), QSettings::IniFormat);
+ color = settings.value("color/high", color).toString();
+ }
+
+ static QString GetConfigFilePath() {
+ return QCoreApplication::applicationDirPath() + QDir::separator() + "conf.ini";
+ }
+
+ static QString GetAppVersion() {
+ return APP_VERSION;
+ }
+
private:
- GlobalHelper() {}
+ GlobalHelper() {
+ }
};
-#endif //TACTILEIPC3D_GLOBALHELPER_H
+#endif // TACTILEIPC3D_GLOBALHELPER_H
diff --git a/src/glwidget.cpp b/src/glwidget.cpp
index a6bc530..166f056 100644
--- a/src/glwidget.cpp
+++ b/src/glwidget.cpp
@@ -3,11 +3,7 @@
//
#include "glwidget.h"
-#include
-#include
-#include
-#include
-#include
+
#include
#include
#include
@@ -15,1688 +11,1713 @@
#include
#include
#include
-#include
-#include
-#include
-#include
-#include
+
+#include
+#include
+#include
#include
+#include
+#include
+#include
#include
+#include
+#include
+#include
+#include
+#include
#include
-#include
-#include
#include
+#include
+#include
+
#include "globalhelper.h"
-// 读取文本文件内容(这里主要用来从 Qt Resource `:/shaders/...` 读取 shader 源码)
+
+// 读取文本文件内容(这里主要用来从 Qt Resource `:/shaders/...` 读取 shader
+// 源码)
static QByteArray readFile(const QString& path) {
- QFile f(path);
- if (!f.open(QIODevice::ReadOnly)) return {};
- return f.readAll();
+ QFile f(path);
+ if (!f.open(QIODevice::ReadOnly)) return {};
+ return f.readAll();
}
// 简单的 4x4 单位矩阵(目前只用于保留的 float[16] 版本)
static void matIdentity(float m[16]) {
- for (int i = 0; i < 16; i++) {
- m[i] = 0;
- }
+ for (int i = 0; i < 16; i++) {
+ m[i] = 0;
+ }
- m[0] = m[5] = m[10] = m[15] = 1;
+ m[0] = m[5] = m[10] = m[15] = 1;
}
static QVector3D toColorVec(const QColor& color) {
- return QVector3D(color.redF(), color.greenF(), color.blueF());
+ return QVector3D(color.redF(), color.greenF(), color.blueF());
}
-GLWidget::GLWidget(QWidget *parent)
- : QOpenGLWidget(parent) {
- setMinimumSize(640, 480);
+GLWidget::GLWidget(QWidget* parent) : QOpenGLWidget(parent) {
+ setMinimumSize(640, 480);
}
GLWidget::~GLWidget() {
- makeCurrent();
- delete m_bgProg;
- delete m_panelProg;
- delete m_dotsProg;
- delete m_heatmapProg;
- delete m_baseProg;
+ makeCurrent();
+ delete m_bgProg;
+ delete m_panelProg;
+ delete m_dotsProg;
+ delete m_heatmapProg;
+ delete m_baseProg;
- if (m_panelIbo)
- glDeleteBuffers(1, &m_panelIbo);
- if (m_panelVbo)
- glDeleteBuffers(1, &m_panelVbo);
- if (m_panelVao)
- glDeleteVertexArrays(1, &m_panelVao);
- if (m_dotsVao)
- glDeleteVertexArrays(1, &m_dotsVao);
- if (m_dotsVbo)
- glDeleteBuffers(1, &m_dotsVbo);
- if (m_instanceVbo)
- glDeleteBuffers(1, &m_instanceVbo);
- if (m_bgVbo)
- glDeleteBuffers(1, &m_bgVbo);
- if (m_bgVao)
- glDeleteVertexArrays(1, &m_bgVao);
+ if (m_panelIbo) glDeleteBuffers(1, &m_panelIbo);
+ if (m_panelVbo) glDeleteBuffers(1, &m_panelVbo);
+ if (m_panelVao) glDeleteVertexArrays(1, &m_panelVao);
+ if (m_dotsVao) glDeleteVertexArrays(1, &m_dotsVao);
+ if (m_dotsVbo) glDeleteBuffers(1, &m_dotsVbo);
+ if (m_instanceVbo) glDeleteBuffers(1, &m_instanceVbo);
+ if (m_bgVbo) glDeleteBuffers(1, &m_bgVbo);
+ if (m_bgVao) glDeleteVertexArrays(1, &m_bgVao);
- 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();
+ 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();
}
void GLWidget::setPanelSize(float w, float h, float d) {
- m_panelW = w;
- m_panelH = h;
- m_panelD = d;
- m_panelGeometryDirty = true;
- m_heightDirty = true;
- update();
+ m_panelW = w;
+ m_panelH = h;
+ m_panelD = d;
+ m_panelGeometryDirty = true;
+ m_heightDirty = true;
+ update();
}
void GLWidget::setPanelThickness(float h) {
- if (qFuzzyCompare(m_panelH, h))
- return;
- m_panelD = h;
- m_panelGeometryDirty = true;
- m_heightDirty = true;
- update();
+ if (qFuzzyCompare(m_panelH, h)) return;
+ m_panelD = h;
+ m_panelGeometryDirty = true;
+ m_heightDirty = true;
+ update();
}
void GLWidget::setSpec(int rows, int cols, float pitch, float dotRaius) {
- m_rows = qMax(0, rows);
- m_cols = qMax(0, cols);
- m_pitch = qMax(0.0f, pitch);
- m_dotRadius = qMax(0.0f, dotRaius);
+ m_rows = qMax(0, rows);
+ m_cols = qMax(0, cols);
+ m_pitch = qMax(0.0f, pitch);
+ m_dotRadius = qMax(0.0f, dotRaius);
- // 自动根据点阵尺寸调整 panel 的尺寸(保证圆点不会越过顶面边界)。
- // 约定:点中心覆盖的范围是 (cols-1)*pitch / (rows-1)*pitch,面板需要额外留出 dotRadius 的边缘空间。
- const float gridW = float(qMax(0, m_cols - 1)) * m_pitch;
- const float gridH = float(qMax(0, m_rows - 1)) * m_pitch;
- m_panelW = gridW + 2.0f * m_dotRadius;
- m_panelH = gridH + 2.0f * m_dotRadius;
- m_panelGeometryDirty = true;
- m_dotsGeometryDirty = true;
+ // 自动根据点阵尺寸调整 panel 的尺寸(保证圆点不会越过顶面边界)。
+ // 约定:点中心覆盖的范围是 (cols-1)*pitch / (rows-1)*pitch,面板需要额外留出
+ // dotRadius 的边缘空间。
+ const float gridW = float(qMax(0, m_cols - 1)) * m_pitch;
+ const float gridH = float(qMax(0, m_rows - 1)) * m_pitch;
+ m_panelW = gridW + 2.0f * m_dotRadius;
+ m_panelH = gridH + 2.0f * m_dotRadius;
+ m_panelGeometryDirty = true;
+ m_dotsGeometryDirty = true;
- QMutexLocker lk(&m_dataMutex);
- m_latestValues.resize(dotCount());
- for (int i = 0; i < m_latestValues.size(); ++i) {
- m_latestValues[i] = m_min;
- }
- m_valuesDirty = true;
- m_hasData = false;
- m_heightDirty = true;
- update();
+ QMutexLocker lk(&m_dataMutex);
+ m_latestValues.resize(dotCount());
+ for (int i = 0; i < m_latestValues.size(); ++i) {
+ m_latestValues[i] = m_min;
+ }
+ m_valuesDirty = true;
+ m_hasData = false;
+ m_heightDirty = true;
+ update();
}
-void GLWidget::submitValues(const QVector &values) {
- if (values.size() != dotCount()) return;
+void GLWidget::submitValues(const QVector& values) {
+ if (values.size() != dotCount()) return;
- {
- QMutexLocker lk(&m_dataMutex);
- m_latestValues = values;
- m_valuesDirty = true;
- m_hasData = true;
- m_heightDirty = true;
- }
+ {
+ QMutexLocker lk(&m_dataMutex);
+ m_latestValues = values;
+ m_valuesDirty = true;
+ m_hasData = true;
+ m_heightDirty = true;
+ }
- update();
+ update();
}
void GLWidget::setRange(int minV, int maxV) {
- m_min = minV;
- m_max = maxV;
- m_heightDirty = true;
- update();
+ m_min = minV;
+ m_max = maxV;
+ m_heightDirty = true;
+ update();
}
void GLWidget::setYaw(float yawDeg) {
- if (qFuzzyCompare(m_camYawDeg, yawDeg))
- return;
- m_camYawDeg = yawDeg;
- emit yawChanged();
- update();
+ if (qFuzzyCompare(m_camYawDeg, yawDeg)) return;
+ m_camYawDeg = yawDeg;
+ emit yawChanged();
+ update();
}
-void GLWidget::setRenderModeString(const QString &mode) {
- RenderMode next = DataViz;
- if (mode.compare(QStringLiteral("realistic"), Qt::CaseInsensitive) == 0) {
- next = Realistic;
- } else if (mode.compare(QStringLiteral("dataViz"), Qt::CaseInsensitive) == 0) {
- next = DataViz;
- }
+void GLWidget::setRenderModeString(const QString& mode) {
+ RenderMode next = DataViz;
+ if (mode.compare(QStringLiteral("realistic"), Qt::CaseInsensitive) == 0) {
+ next = Realistic;
+ } else if (mode.compare(QStringLiteral("dataViz"), Qt::CaseInsensitive) ==
+ 0) {
+ next = DataViz;
+ }
- if (m_renderMode == next)
- return;
- m_renderMode = next;
- update();
+ if (m_renderMode == next) return;
+ m_renderMode = next;
+ update();
}
-void GLWidget::setLabelModeString(const QString &mode) {
- LabelMode next = LabelsOff;
- if (mode.compare(QStringLiteral("hover"), Qt::CaseInsensitive) == 0) {
- next = LabelsHover;
- } else if (mode.compare(QStringLiteral("always"), Qt::CaseInsensitive) == 0) {
- next = LabelsAlways;
- } else if (mode.compare(QStringLiteral("off"), Qt::CaseInsensitive) == 0) {
- next = LabelsOff;
- }
+void GLWidget::setLabelModeString(const QString& mode) {
+ LabelMode next = LabelsOff;
+ if (mode.compare(QStringLiteral("hover"), Qt::CaseInsensitive) == 0) {
+ next = LabelsHover;
+ } else if (mode.compare(QStringLiteral("always"), Qt::CaseInsensitive) == 0) {
+ next = LabelsAlways;
+ } else if (mode.compare(QStringLiteral("off"), Qt::CaseInsensitive) == 0) {
+ next = LabelsOff;
+ }
- if (m_labelMode == next)
- return;
- m_labelMode = next;
- if (m_labelMode == LabelsOff)
- m_hoveredIndex = -1;
- update();
+ if (m_labelMode == next) return;
+ m_labelMode = next;
+ if (m_labelMode == LabelsOff) m_hoveredIndex = -1;
+ update();
}
void GLWidget::setLightMode(bool on = true) {
- if (on == m_lightMode) {
- return;
- }
+ if (on == m_lightMode) {
+ return;
+ }
- m_lightMode = on;
- update();
+ m_lightMode = on;
+ update();
}
void GLWidget::setShowBg(bool on = true) {
- if (on == m_showBg) {
- return;
- }
- m_showBg = on;
- update();
+ if (on == m_showBg) {
+ return;
+ }
+ m_showBg = on;
+ update();
}
void GLWidget::setColorLow(const QColor& color) {
- const QVector3D next = toColorVec(color);
- if (m_colorLow == next)
- return;
- m_colorLow = next;
- update();
+ const QVector3D next = toColorVec(color);
+ if (m_colorLow == next) return;
+ m_colorLow = next;
+ update();
+}
+
+void GLWidget::setColorZero(const QColor& color) {
+ const QVector3D next = toColorVec(color);
+ if (m_colorZero == next) return;
+ m_colorZero = next;
+ update();
}
void GLWidget::setColorMid(const QColor& color) {
- const QVector3D next = toColorVec(color);
- if (m_colorMid == next)
- return;
- m_colorMid = next;
- update();
+ const QVector3D next = toColorVec(color);
+ if (m_colorMid == next) return;
+ m_colorMid = next;
+ update();
}
void GLWidget::setColorHigh(const QColor& color) {
- const QVector3D next = toColorVec(color);
- if (m_colorHigh == next)
- return;
- m_colorHigh = next;
- update();
+ const QVector3D next = toColorVec(color);
+ if (m_colorHigh == next) return;
+ m_colorHigh = next;
+ update();
}
void GLWidget::initializeGL() {
- initializeOpenGLFunctions();
+ initializeOpenGLFunctions();
- // 基础状态:开启深度测试,否则 3D 物体的遮挡关系会不正确
- glEnable(GL_DEPTH_TEST);
- glDepthFunc(GL_LESS);
- initPrograms_();
- initDotTexture_();
- initBackgroundGeometry_();
- initPanelGeometry_();
- initDotGeometry_();
- initRoomGeometry_();
- initBaseGeometry_();
- initHeatmapGeometry_();
- initSkirtGeometry_();
- updateHeatmapData_();
- initHeightTexture_();
- updateSkirtVertices_();
- m_panelGeometryDirty = false;
- m_dotsGeometryDirty = false;
+ // 基础状态:开启深度测试,否则 3D 物体的遮挡关系会不正确
+ glEnable(GL_DEPTH_TEST);
+ glDepthFunc(GL_LESS);
+ initPrograms_();
+ initDotTexture_();
+ initBackgroundGeometry_();
+ initPanelGeometry_();
+ initDotGeometry_();
+ initRoomGeometry_();
+ initBaseGeometry_();
+ initHeatmapGeometry_();
+ initSkirtGeometry_();
+ updateHeatmapData_();
+ initHeightTexture_();
+ updateSkirtVertices_();
+ m_panelGeometryDirty = false;
+ m_dotsGeometryDirty = false;
- matIdentity(m_modelPanel);
- matIdentity(m_view);
- matIdentity(m_proj);
+ matIdentity(m_modelPanel);
+ matIdentity(m_view);
+ matIdentity(m_proj);
}
void GLWidget::initGeometry_() {
- initDotTexture_();
- initBackgroundGeometry_();
+ initDotTexture_();
+ initBackgroundGeometry_();
+ initPanelGeometry_();
+ initDotGeometry_();
+ initRoomGeometry_();
+ initBaseGeometry_();
+ initHeatmapGeometry_();
+ initSkirtGeometry_();
+ updateHeatmapData_();
+ initHeightTexture_();
+ updateSkirtVertices_();
+}
+
+void GLWidget::resizeGL(int w, int h) { glViewport(0, 0, w, h); }
+
+void GLWidget::paintGL() {
+ QPainter painter(this);
+ painter.setRenderHint(QPainter::Antialiasing, true);
+ painter.setRenderHint(QPainter::TextAntialiasing, true);
+
+ painter.beginNativePainting();
+
+ glClearColor(1.0f, 1.0f, 1.0f, 1.0f);
+ glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
+
+ // 如果点阵规格/面板尺寸发生变化,需要在有 GL 上下文时重建几何体 buffer。
+ if (m_panelGeometryDirty) {
initPanelGeometry_();
- initDotGeometry_();
- initRoomGeometry_();
initBaseGeometry_();
initHeatmapGeometry_();
initSkirtGeometry_();
+ m_heightDirty = true;
+ m_panelGeometryDirty = false;
+ }
+ if (m_dotsGeometryDirty) {
+ initDotGeometry_();
+ m_dotsGeometryDirty = false;
+ m_valuesDirty = true; // instanceVBO 重新分配后需要重新上传数据
+ }
+
+ // --- 背景:屏幕空间网格(不随相机旋转)---
+ if (m_bgProg && m_bgVao && m_showBg) {
+ const float dpr = devicePixelRatioF();
+ const QVector2D viewport(float(width()) * dpr, float(height()) * dpr);
+
+ glDisable(GL_DEPTH_TEST);
+ glDepthMask(GL_FALSE);
+
+ m_bgProg->bind();
+ m_bgProg->setUniformValue("uViewport", viewport);
+ m_bgProg->setUniformValue("uMinorStep", 24.0f * dpr);
+ m_bgProg->setUniformValue("uMajorStep", 120.0f * dpr);
+
+ glBindVertexArray(m_bgVao);
+ glDrawArrays(GL_TRIANGLES, 0, 6);
+ glBindVertexArray(0);
+
+ m_bgProg->release();
+
+ glDepthMask(GL_TRUE);
+ glEnable(GL_DEPTH_TEST);
+ }
+
+ // 1) 更新相机/投影矩阵(MVP),决定如何把 3D 世界投影到屏幕
+ updateMatrices_();
+ updateRoom_();
+ if (m_useHeatmap) {
updateHeatmapData_();
- initHeightTexture_();
+ uploadHeightTexture_();
updateSkirtVertices_();
+ renderBase_();
+ renderSkirt_();
+ renderHeatmap_();
+ } else {
+ // 2) 如果外部提交了新数据,就把 CPU 生成的 instance 数据更新到 GPU
+ updateInstanceBufferIfNeeded_();
+ }
+
+ if (!m_useHeatmap && m_panelProg) {
+ m_panelProg->bind();
+ // uniforms:每次 draw 前设置的一组“常量参数”(对当前 draw call
+ // 的所有顶点/片元都一致) uMVP: Model-View-Projection
+ // 矩阵(把顶点从世界坐标 -> 裁剪空间;gl_Position 必须输出裁剪空间坐标)
+ m_panelProg->setUniformValue("uMVP", m_mvp);
+ m_panelProg->setUniformValue("uCameraPos", m_cameraPos);
+ m_panelProg->setUniformValue("uPanelW", m_panelW);
+ m_panelProg->setUniformValue("uPanelH", m_panelH);
+ m_panelProg->setUniformValue("uPanelD", m_panelD);
+ m_panelProg->setUniformValue("uRows", m_rows);
+ m_panelProg->setUniformValue("uCols", m_cols);
+ m_panelProg->setUniformValue("uPitch", m_pitch);
+ m_panelProg->setUniformValue("uDotRadius", m_dotRadius);
+ 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);
+ m_panelProg->release();
+ }
+
+ if (!m_useHeatmap && m_dotsProg) {
+ m_dotsProg->bind();
+ // uniforms:每次 draw 前设置的一组“常量参数”(对当前 draw call 的所有
+ // instance 都一致) uMVP: 同上;用于把每个 dot 的世界坐标变换到屏幕
+ m_dotsProg->setUniformValue("uMVP", m_mvp);
+ m_dotsProg->setUniformValue("uRenderMode", 1);
+ // uDotRadius: dot 的半径(世界坐标单位)
+ m_dotsProg->setUniformValue("uDotRadius", m_dotRadius);
+ // uBaseY: dot 的高度(放在 panel 顶面上方一点点,避免 z-fighting/闪烁)
+ 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));
+ m_dotsProg->setUniformValue("uColorZero", m_colorZero);
+ m_dotsProg->setUniformValue("uColorLow", m_colorLow);
+ m_dotsProg->setUniformValue("uColorMid", m_colorMid);
+ m_dotsProg->setUniformValue("uColorHigh", m_colorHigh);
+ const int hasData = (m_hasData.load() || m_dotTex == 0) ? 1 : 0;
+ m_dotsProg->setUniformValue("uHasData", hasData);
+ m_dotsProg->setUniformValue("uCameraPos", m_cameraPos);
+ m_dotsProg->setUniformValue("uDotTex", 0);
+ if (m_dotTex) {
+ glActiveTexture(GL_TEXTURE0);
+ glBindTexture(GL_TEXTURE_2D, m_dotTex);
+ }
+
+ glBindVertexArray(m_dotsVao);
+ glDrawArraysInstanced(GL_TRIANGLES, 0, 6, m_instanceCount);
+ glBindVertexArray(0);
+ if (m_dotTex) {
+ glActiveTexture(GL_TEXTURE0);
+ glBindTexture(GL_TEXTURE_2D, 0);
+ }
+
+ // m_dotsProg->release();
+ }
+
+ painter.endNativePainting();
+
+ if (!m_useHeatmap && m_labelMode != LabelsOff && dotCount() > 0) {
+ QVector valuesCopy;
+ {
+ QMutexLocker lk(&m_dataMutex);
+ valuesCopy = m_latestValues;
+ }
+
+ auto projectToScreen = [&](const QVector3D& world, QPointF& out) -> bool {
+ 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;
+ };
+
+ 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;
+
+ QFont font = painter.font();
+ font.setPixelSize(11);
+ painter.setFont(font);
+ const QFontMetrics fm(font);
+
+ auto drawLabel = [&](const QPointF& anchor, const QPointF& offset,
+ const QString& text) {
+ QRectF box = fm.boundingRect(text);
+ box.adjust(-6.0, -3.0, 6.0, 3.0);
+ box.moveCenter(anchor + offset);
+
+ const float margin = 4.0f;
+ if (box.left() < margin) box.moveLeft(margin);
+ if (box.top() < margin) box.moveTop(margin);
+ if (box.right() > width() - margin) box.moveRight(width() - margin);
+ if (box.bottom() > height() - margin) box.moveBottom(height() - margin);
+
+ painter.setPen(Qt::NoPen);
+ painter.setBrush(QColor(0, 0, 0, 150));
+ painter.drawRoundedRect(box, 4.0, 4.0);
+ painter.setPen(QColor(255, 255, 255, 235));
+ painter.drawText(box, Qt::AlignCenter, text);
+ };
+
+ auto dotWorldCenter = [&](int i) -> QVector3D {
+ const int rr = (m_cols > 0) ? (i / m_cols) : 0;
+ const int cc = (m_cols > 0) ? (i % m_cols) : 0;
+ const float x = (cc * m_pitch) - w * 0.5f;
+ const float z = (rr * m_pitch) - h * 0.5f;
+ return QVector3D(x, baseY, z);
+ };
+
+ auto dotRadiusPx = [&](const QVector3D& worldCenter,
+ const QPointF& centerPx) -> float {
+ QPointF edge;
+ if (!projectToScreen(worldCenter + QVector3D(m_dotRadius, 0.0f, 0.0f),
+ edge))
+ return 0.0f;
+ const float dx = float(edge.x() - centerPx.x());
+ const float dy = float(edge.y() - centerPx.y());
+ return std::sqrt(dx * dx + dy * dy);
+ };
+
+ if (m_labelMode == LabelsAlways && valuesCopy.size() == dotCount()) {
+ painter.setPen(QColor(255, 255, 255, 220));
+ for (int i = 0; i < dotCount(); ++i) {
+ QPointF centerPx;
+ const QVector3D world = dotWorldCenter(i);
+ if (!projectToScreen(world, centerPx)) continue;
+
+ const float rPx = dotRadiusPx(world, centerPx);
+ if (rPx < 12.0f) continue;
+
+ const QString text = QString::number(int(valuesCopy[i] + 0.5f));
+ drawLabel(centerPx, QPointF(0.0, 0.0), text);
+ }
+ }
+
+ if (m_hoveredIndex >= 0 && m_hoveredIndex < dotCount() &&
+ valuesCopy.size() == dotCount()) {
+ QPointF centerPx;
+ const QVector3D world = dotWorldCenter(m_hoveredIndex);
+ if (projectToScreen(world, centerPx)) {
+ const float rPx = dotRadiusPx(world, centerPx);
+
+ painter.setBrush(Qt::NoBrush);
+ painter.setPen(QPen(QColor(255, 255, 255, 210), 1.5));
+ painter.drawEllipse(centerPx, rPx * 1.08f, rPx * 1.08f);
+
+ if (m_labelMode == LabelsHover) {
+ const QString text =
+ QString::number(int(valuesCopy[m_hoveredIndex] + 0.5f));
+ drawLabel(centerPx, QPointF(0.0, -rPx - 12.0f), text);
+ }
+ }
+ }
+ }
+
}
-void GLWidget::resizeGL(int w, int h) {
- glViewport(0, 0, w, h);
-}
-
-void GLWidget::paintGL() {
- glClearColor(1.0f, 1.0f, 1.0f, 1.0f);
- glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
-
- // 如果点阵规格/面板尺寸发生变化,需要在有 GL 上下文时重建几何体 buffer。
- if (m_panelGeometryDirty) {
- initPanelGeometry_();
- initBaseGeometry_();
- initHeatmapGeometry_();
- initSkirtGeometry_();
- m_heightDirty = true;
- m_panelGeometryDirty = false;
- }
- if (m_dotsGeometryDirty) {
- initDotGeometry_();
- m_dotsGeometryDirty = false;
- m_valuesDirty = true; // instanceVBO 重新分配后需要重新上传数据
- }
-
- // --- 背景:屏幕空间网格(不随相机旋转)---
- if (m_bgProg && m_bgVao && m_showBg) {
- const float dpr = devicePixelRatioF();
- const QVector2D viewport(float(width()) * dpr, float(height()) * dpr);
-
- glDisable(GL_DEPTH_TEST);
- glDepthMask(GL_FALSE);
-
- m_bgProg->bind();
- m_bgProg->setUniformValue("uViewport", viewport);
- m_bgProg->setUniformValue("uMinorStep", 24.0f * dpr);
- m_bgProg->setUniformValue("uMajorStep", 120.0f * dpr);
-
- glBindVertexArray(m_bgVao);
- glDrawArrays(GL_TRIANGLES, 0, 6);
- glBindVertexArray(0);
-
- m_bgProg->release();
-
- glDepthMask(GL_TRUE);
- glEnable(GL_DEPTH_TEST);
- }
-
- // 1) 更新相机/投影矩阵(MVP),决定如何把 3D 世界投影到屏幕
- updateMatrices_();
- updateRoom_();
+void GLWidget::mousePressEvent(QMouseEvent* event) {
+ m_lastPos = event->pos();
+ if (event->button() == Qt::RightButton) {
+ m_rightDown = true;
+ event->accept();
+ return;
+ }
+ if (event->button() == Qt::LeftButton) {
if (m_useHeatmap) {
- updateHeatmapData_();
- uploadHeightTexture_();
- updateSkirtVertices_();
- renderBase_();
- renderSkirt_();
- renderHeatmap_();
- } else {
- // 2) 如果外部提交了新数据,就把 CPU 生成的 instance 数据更新到 GPU
- updateInstanceBufferIfNeeded_();
+ event->accept();
+ return;
}
-
- if (!m_useHeatmap && m_panelProg) {
- m_panelProg->bind();
- // uniforms:每次 draw 前设置的一组“常量参数”(对当前 draw call 的所有顶点/片元都一致)
- // uMVP: Model-View-Projection 矩阵(把顶点从世界坐标 -> 裁剪空间;gl_Position 必须输出裁剪空间坐标)
- m_panelProg->setUniformValue("uMVP", m_mvp);
- m_panelProg->setUniformValue("uCameraPos", m_cameraPos);
- m_panelProg->setUniformValue("uPanelW", m_panelW);
- m_panelProg->setUniformValue("uPanelH", m_panelH);
- m_panelProg->setUniformValue("uPanelD", m_panelD);
- m_panelProg->setUniformValue("uRows", m_rows);
- m_panelProg->setUniformValue("uCols", m_cols);
- m_panelProg->setUniformValue("uPitch", m_pitch);
- m_panelProg->setUniformValue("uDotRadius", m_dotRadius);
- 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);
- m_panelProg->release();
+ QVector3D world;
+ const int index = pickDotIndex_(event->pos(), &world);
+ qInfo() << "clicked index: " << index;
+ 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);
}
-
- if (!m_useHeatmap && m_dotsProg) {
- m_dotsProg->bind();
- // uniforms:每次 draw 前设置的一组“常量参数”(对当前 draw call 的所有 instance 都一致)
- // uMVP: 同上;用于把每个 dot 的世界坐标变换到屏幕
- m_dotsProg->setUniformValue("uMVP", m_mvp);
- m_dotsProg->setUniformValue("uRenderMode", 1);
- // uDotRadius: dot 的半径(世界坐标单位)
- m_dotsProg->setUniformValue("uDotRadius", m_dotRadius);
- // uBaseY: dot 的高度(放在 panel 顶面上方一点点,避免 z-fighting/闪烁)
- 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));
- m_dotsProg->setUniformValue("uColorLow", m_colorLow);
- m_dotsProg->setUniformValue("uColorMid", m_colorMid);
- m_dotsProg->setUniformValue("uColorHigh", m_colorHigh);
- const int hasData = (m_hasData.load() || m_dotTex == 0) ? 1 : 0;
- m_dotsProg->setUniformValue("uHasData", hasData);
- m_dotsProg->setUniformValue("uCameraPos", m_cameraPos);
- m_dotsProg->setUniformValue("uDotTex", 0);
- if (m_dotTex) {
- glActiveTexture(GL_TEXTURE0);
- glBindTexture(GL_TEXTURE_2D, m_dotTex);
- }
-
- glBindVertexArray(m_dotsVao);
- glDrawArraysInstanced(GL_TRIANGLES, 0, 6, m_instanceCount);
- glBindVertexArray(0);
- if (m_dotTex) {
- glActiveTexture(GL_TEXTURE0);
- glBindTexture(GL_TEXTURE_2D, 0);
- }
-
- // m_dotsProg->release();
- }
-
- if (!m_useHeatmap && m_labelMode != LabelsOff && dotCount() > 0) {
- QVector valuesCopy;
- {
- QMutexLocker lk(&m_dataMutex);
- valuesCopy = m_latestValues;
- }
-
- auto projectToScreen = [&](const QVector3D& world, QPointF& out) -> bool {
- 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;
- };
-
- 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;
-
- QPainter painter(this);
- painter.setRenderHint(QPainter::Antialiasing, true);
- painter.setRenderHint(QPainter::TextAntialiasing, true);
-
- QFont font = painter.font();
- font.setPixelSize(11);
- painter.setFont(font);
- const QFontMetrics fm(font);
-
- auto drawLabel = [&](const QPointF& anchor, const QPointF& offset, const QString& text) {
- QRectF box = fm.boundingRect(text);
- box.adjust(-6.0, -3.0, 6.0, 3.0);
- box.moveCenter(anchor + offset);
-
- const float margin = 4.0f;
- if (box.left() < margin) box.moveLeft(margin);
- if (box.top() < margin) box.moveTop(margin);
- if (box.right() > width() - margin) box.moveRight(width() - margin);
- if (box.bottom() > height() - margin) box.moveBottom(height() - margin);
-
- painter.setPen(Qt::NoPen);
- painter.setBrush(QColor(0, 0, 0, 150));
- painter.drawRoundedRect(box, 4.0, 4.0);
- painter.setPen(QColor(255, 255, 255, 235));
- painter.drawText(box, Qt::AlignCenter, text);
- };
-
- auto dotWorldCenter = [&](int i) -> QVector3D {
- const int rr = (m_cols > 0) ? (i / m_cols) : 0;
- const int cc = (m_cols > 0) ? (i % m_cols) : 0;
- const float x = (cc * m_pitch) - w * 0.5f;
- const float z = (rr * m_pitch) - h * 0.5f;
- return QVector3D(x, baseY, z);
- };
-
- auto dotRadiusPx = [&](const QVector3D& worldCenter, const QPointF& centerPx) -> float {
- QPointF edge;
- if (!projectToScreen(worldCenter + QVector3D(m_dotRadius, 0.0f, 0.0f), edge))
- return 0.0f;
- const float dx = float(edge.x() - centerPx.x());
- const float dy = float(edge.y() - centerPx.y());
- return std::sqrt(dx * dx + dy * dy);
- };
-
- if (m_labelMode == LabelsAlways && valuesCopy.size() == dotCount()) {
- painter.setPen(QColor(255, 255, 255, 220));
- for (int i = 0; i < dotCount(); ++i) {
- QPointF centerPx;
- const QVector3D world = dotWorldCenter(i);
- if (!projectToScreen(world, centerPx))
- continue;
-
- const float rPx = dotRadiusPx(world, centerPx);
- if (rPx < 12.0f)
- continue;
-
- const QString text = QString::number(int(valuesCopy[i] + 0.5f));
- drawLabel(centerPx, QPointF(0.0, 0.0), text);
- }
- }
-
- if (m_hoveredIndex >= 0 && m_hoveredIndex < dotCount() && valuesCopy.size() == dotCount()) {
- QPointF centerPx;
- const QVector3D world = dotWorldCenter(m_hoveredIndex);
- if (projectToScreen(world, centerPx)) {
- const float rPx = dotRadiusPx(world, centerPx);
-
- painter.setBrush(Qt::NoBrush);
- painter.setPen(QPen(QColor(255, 255, 255, 210), 1.5));
- painter.drawEllipse(centerPx, rPx * 1.08f, rPx * 1.08f);
-
- if (m_labelMode == LabelsHover) {
- const QString text = QString::number(int(valuesCopy[m_hoveredIndex] + 0.5f));
- drawLabel(centerPx, QPointF(0.0, -rPx - 12.0f), text);
- }
- }
- }
- }
-
- QPainter uiPainter(this);
- drawToggleButton_(&uiPainter);
+ }
+ QOpenGLWidget::mousePressEvent(event);
}
-void GLWidget::mousePressEvent(QMouseEvent *event) {
- m_lastPos = event->pos();
- if (event->button() == Qt::RightButton) {
- m_rightDown = true;
- event->accept();
- 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;
- 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);
-}
+void GLWidget::mouseMoveEvent(QMouseEvent* event) {
+ const QPoint delta = event->pos() - m_lastPos;
+ m_lastPos = event->pos();
-void GLWidget::mouseMoveEvent(QMouseEvent *event) {
- const QPoint delta = event->pos() - m_lastPos;
- m_lastPos = event->pos();
-
- if (m_rightDown) {
- m_camYawDeg += delta.x() * 0.3f;
- m_camPitchDeg = qBound(-89.0f, m_camPitchDeg + delta.y() * 0.3f, 89.0f);
- emit yawChanged();
- update();
- event->accept();
- return;
- }
-
- 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)
- 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;
- };
-
- const QPoint mousePos = event->pos();
- 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::infinity();
- 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 float x = (cc * m_pitch) - w * 0.5f;
- const float z = (rr * m_pitch) - h * 0.5f;
-
- QPointF center;
- const QVector3D worldCenter(x, baseY, z);
- if (!projectToScreen(worldCenter, center))
- 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(mousePos.x()) - float(center.x());
- const float dy = float(mousePos.y()) - float(center.y());
- const float dist2 = dx * dx + dy * dy;
- if (dist2 <= threshold * threshold && dist2 < bestDist2) {
- best = i;
- bestDist2 = dist2;
- }
- }
-
- if (best != m_hoveredIndex) {
- m_hoveredIndex = best;
- update();
- }
- }
-
- QOpenGLWidget::mouseMoveEvent(event);
-}
-
-void GLWidget::mouseReleaseEvent(QMouseEvent *event) {
- if (event->button() == Qt::RightButton) {
- m_rightDown = false;
- event->accept();
- return;
- }
- QOpenGLWidget::mouseReleaseEvent(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, 90.0f);
+ if (m_rightDown) {
+ m_camYawDeg += delta.x() * 0.3f;
+ m_camPitchDeg = qBound(-89.0f, m_camPitchDeg + delta.y() * 0.3f, 89.0f);
+ emit yawChanged();
update();
event->accept();
-}
+ return;
+ }
-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;
+ 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) 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;
+ out.setX((ndc.x() * 0.5f + 0.5f) * float(width()));
+ out.setY((1.0f - (ndc.y() * 0.5f + 0.5f)) * float(height()));
+ return true;
+ };
+ const QPoint mousePos = event->pos();
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::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
- );
+ const int rr = (m_cols > 0) ? (i / m_cols) : 0;
+ const int cc = (m_cols > 0) ? (i % m_cols) : 0;
+ const float x = (cc * m_pitch) - w * 0.5f;
+ const float z = (rr * m_pitch) - h * 0.5f;
- QPointF center;
- if (!projectToScreen_(worldCenter, ¢er))
- continue;
+ QPointF center;
+ const QVector3D worldCenter(x, baseY, z);
+ if (!projectToScreen(worldCenter, center)) continue;
- QPointF edge;
- if (!projectToScreen_(worldCenter + QVector3D(m_dotRadius, 0.0f, 0.0f), &edge))
- 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 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;
- }
+ const float dx = float(mousePos.x()) - float(center.x());
+ const float dy = float(mousePos.y()) - float(center.y());
+ const float dist2 = dx * dx + dy * dy;
+ if (dist2 <= threshold * threshold && dist2 < bestDist2) {
+ best = i;
+ bestDist2 = dist2;
+ }
}
- if (best >= 0 && worldOut)
- *worldOut = bestWorld;
- return best;
+ if (best != m_hoveredIndex) {
+ m_hoveredIndex = best;
+ update();
+ }
+ }
+
+ QOpenGLWidget::mouseMoveEvent(event);
+}
+
+void GLWidget::mouseReleaseEvent(QMouseEvent* event) {
+ if (event->button() == Qt::RightButton) {
+ m_rightDown = false;
+ event->accept();
+ return;
+ }
+ QOpenGLWidget::mouseReleaseEvent(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, 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::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);
- m_bgVbo = 0;
- }
- if (m_bgVao) {
- glDeleteVertexArrays(1, &m_bgVao);
- m_bgVao = 0;
- }
+ if (m_bgVbo) {
+ glDeleteBuffers(1, &m_bgVbo);
+ m_bgVbo = 0;
+ }
+ if (m_bgVao) {
+ glDeleteVertexArrays(1, &m_bgVao);
+ m_bgVao = 0;
+ }
- // 全屏 quad(两个三角形),坐标直接是裁剪空间 NDC [-1,1]
- const float verts[] = {
- -1.0f, -1.0f,
- 1.0f, -1.0f,
- 1.0f, 1.0f,
- -1.0f, -1.0f,
- 1.0f, 1.0f,
- -1.0f, 1.0f,
- };
+ // 全屏 quad(两个三角形),坐标直接是裁剪空间 NDC [-1,1]
+ const float verts[] = {
+ -1.0f, -1.0f, 1.0f, -1.0f, 1.0f, 1.0f,
+ -1.0f, -1.0f, 1.0f, 1.0f, -1.0f, 1.0f,
+ };
- glGenVertexArrays(1, &m_bgVao);
- glBindVertexArray(m_bgVao);
+ glGenVertexArrays(1, &m_bgVao);
+ glBindVertexArray(m_bgVao);
- glGenBuffers(1, &m_bgVbo);
- glBindBuffer(GL_ARRAY_BUFFER, m_bgVbo);
- glBufferData(GL_ARRAY_BUFFER, sizeof(verts), verts, GL_STATIC_DRAW);
+ glGenBuffers(1, &m_bgVbo);
+ glBindBuffer(GL_ARRAY_BUFFER, m_bgVbo);
+ glBufferData(GL_ARRAY_BUFFER, sizeof(verts), verts, GL_STATIC_DRAW);
- glEnableVertexAttribArray(0);
- glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(float), (void*)0);
+ glEnableVertexAttribArray(0);
+ glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(float), (void*)0);
- glBindVertexArray(0);
+ glBindVertexArray(0);
}
void GLWidget::initPanelGeometry_() {
- // panel:目前只画“顶面矩形”,y 固定在 +panelH/2
- if (m_panelIbo) {
- glDeleteBuffers(1, &m_panelIbo);
- m_panelIbo = 0;
- }
- if (m_panelVbo) {
- glDeleteBuffers(1, &m_panelVbo);
- m_panelVbo = 0;
- }
- if (m_panelVao) {
- glDeleteVertexArrays(1, &m_panelVao);
- m_panelVao = 0;
- }
+ // panel:目前只画“顶面矩形”,y 固定在 +panelH/2
+ if (m_panelIbo) {
+ glDeleteBuffers(1, &m_panelIbo);
+ m_panelIbo = 0;
+ }
+ if (m_panelVbo) {
+ glDeleteBuffers(1, &m_panelVbo);
+ m_panelVbo = 0;
+ }
+ if (m_panelVao) {
+ glDeleteVertexArrays(1, &m_panelVao);
+ m_panelVao = 0;
+ }
- const float y = m_panelH * 0.5f;
- const float hw = m_panelW * 0.5;
- const float hd = m_panelD * 0.5f;
+ const float y = m_panelH * 0.5f;
+ const float hw = m_panelW * 0.5;
+ const float hd = m_panelD * 0.5f;
- struct V {
- float x, y, z;
- float nx, ny, nz;
- };
- V verts[24] = {
- // +Y 顶面 (normal 0, +1, 0)
- {-hw, +y, -hd, 0, +1, 0}, // 0
- {+hw, +y, -hd, 0, +1, 0}, // 1
- {+hw, +y, +hd, 0, +1, 0}, // 2
- {-hw, +y, +hd, 0, +1, 0}, // 3
+ struct V {
+ float x, y, z;
+ float nx, ny, nz;
+ };
+ V verts[24] = {
+ // +Y 顶面 (normal 0, +1, 0)
+ {-hw, +y, -hd, 0, +1, 0}, // 0
+ {+hw, +y, -hd, 0, +1, 0}, // 1
+ {+hw, +y, +hd, 0, +1, 0}, // 2
+ {-hw, +y, +hd, 0, +1, 0}, // 3
- // +Z 前面 (normal 0, 0, +1)
- {-hw, +y, +hd, 0, 0, +1}, // 4
- {+hw, +y, +hd, 0, 0, +1}, // 5
- {+hw, -y, +hd, 0, 0, +1}, // 6
- {-hw, -y, +hd, 0, 0, +1}, // 7
+ // +Z 前面 (normal 0, 0, +1)
+ {-hw, +y, +hd, 0, 0, +1}, // 4
+ {+hw, +y, +hd, 0, 0, +1}, // 5
+ {+hw, -y, +hd, 0, 0, +1}, // 6
+ {-hw, -y, +hd, 0, 0, +1}, // 7
- // -Y 底面 (normal 0, -1, 0)
- {-hw, -y, +hd, 0, -1, 0}, // 8
- {+hw, -y, +hd, 0, -1, 0}, // 9
- {+hw, -y, -hd, 0, -1, 0}, // 10
- {-hw, -y, -hd, 0, -1, 0}, // 11
+ // -Y 底面 (normal 0, -1, 0)
+ {-hw, -y, +hd, 0, -1, 0}, // 8
+ {+hw, -y, +hd, 0, -1, 0}, // 9
+ {+hw, -y, -hd, 0, -1, 0}, // 10
+ {-hw, -y, -hd, 0, -1, 0}, // 11
- // -Z 后面 (normal 0, 0, -1)
- {+hw, +y, -hd, 0, 0, -1}, // 12
- {-hw, +y, -hd, 0, 0, -1}, // 13
- {-hw, -y, -hd, 0, 0, -1}, // 14
- {+hw, -y, -hd, 0, 0, -1}, // 15
+ // -Z 后面 (normal 0, 0, -1)
+ {+hw, +y, -hd, 0, 0, -1}, // 12
+ {-hw, +y, -hd, 0, 0, -1}, // 13
+ {-hw, -y, -hd, 0, 0, -1}, // 14
+ {+hw, -y, -hd, 0, 0, -1}, // 15
- // -X 左面 (normal -1, 0, 0)
- {-hw, +y, -hd, -1, 0, 0}, // 16
- {-hw, +y, +hd, -1, 0, 0}, // 17
- {-hw, -y, +hd, -1, 0, 0}, // 18
- {-hw, -y, -hd, -1, 0, 0}, // 19
+ // -X 左面 (normal -1, 0, 0)
+ {-hw, +y, -hd, -1, 0, 0}, // 16
+ {-hw, +y, +hd, -1, 0, 0}, // 17
+ {-hw, -y, +hd, -1, 0, 0}, // 18
+ {-hw, -y, -hd, -1, 0, 0}, // 19
- // +X 右面 (normal +1, 0, 0)
- {+hw, +y, +hd, +1, 0, 0}, // 20
- {+hw, +y, -hd, +1, 0, 0}, // 21
- {+hw, -y, -hd, +1, 0, 0}, // 22
- {+hw, -y, +hd, +1, 0, 0}, // 23
- };
+ // +X 右面 (normal +1, 0, 0)
+ {+hw, +y, +hd, +1, 0, 0}, // 20
+ {+hw, +y, -hd, +1, 0, 0}, // 21
+ {+hw, -y, -hd, +1, 0, 0}, // 22
+ {+hw, -y, +hd, +1, 0, 0}, // 23
+ };
- unsigned int idx[36] = {
- 0, 1, 2, 0, 2, 3, // top
- 4, 5, 6, 4, 6, 7, // front
- 8, 9, 10, 8, 10,11, // bottom
- 12,13,14, 12,14,15, // back
- 16,17,18, 16,18,19, // left
- 20,21,22, 20,22,23 // right
- };
+ unsigned int idx[36] = {
+ 0, 1, 2, 0, 2, 3, // top
+ 4, 5, 6, 4, 6, 7, // front
+ 8, 9, 10, 8, 10, 11, // bottom
+ 12, 13, 14, 12, 14, 15, // back
+ 16, 17, 18, 16, 18, 19, // left
+ 20, 21, 22, 20, 22, 23 // right
+ };
- m_panelIndexCount = 36;
+ m_panelIndexCount = 36;
- glGenVertexArrays(1, &m_panelVao);
- glBindVertexArray(m_panelVao);
+ glGenVertexArrays(1, &m_panelVao);
+ glBindVertexArray(m_panelVao);
- glGenBuffers(1, &m_panelVbo);
- glBindBuffer(GL_ARRAY_BUFFER, m_panelVbo);
- glBufferData(GL_ARRAY_BUFFER, sizeof(verts), verts, GL_STATIC_DRAW);
+ glGenBuffers(1, &m_panelVbo);
+ glBindBuffer(GL_ARRAY_BUFFER, m_panelVbo);
+ glBufferData(GL_ARRAY_BUFFER, sizeof(verts), verts, GL_STATIC_DRAW);
- glGenBuffers(1, &m_panelIbo);
- glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_panelIbo);
- glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(idx), idx, GL_STATIC_DRAW);
+ glGenBuffers(1, &m_panelIbo);
+ glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_panelIbo);
+ glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(idx), idx, GL_STATIC_DRAW);
- // attribute layout:
- // location 0: position (x,y,z)
- // location 1: normal (nx,ny,nz) —— 当前 shader 没用到,先留着
- 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);
+ // attribute layout:
+ // location 0: position (x,y,z)
+ // location 1: normal (nx,ny,nz) —— 当前 shader 没用到,先留着
+ 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::initDotGeometry_() {
- // dot:使用 instanced rendering 画很多个“圆点”(本质是一个 quad + fragment shader 里 discard 成圆)
- // vertex buffer 里只有一个单位 quad,instance buffer 里存每个点的位置和数值。
- if (m_instanceVbo) {
- glDeleteBuffers(1, &m_instanceVbo);
- m_instanceVbo = 0;
- }
- if (m_dotsVbo) {
- glDeleteBuffers(1, &m_dotsVbo);
- m_dotsVbo = 0;
- }
- if (m_dotsVao) {
- glDeleteVertexArrays(1, &m_dotsVao);
- m_dotsVao = 0;
- }
+ // dot:使用 instanced rendering 画很多个“圆点”(本质是一个 quad + fragment
+ // shader 里 discard 成圆) vertex buffer 里只有一个单位 quad,instance buffer
+ // 里存每个点的位置和数值。
+ if (m_instanceVbo) {
+ glDeleteBuffers(1, &m_instanceVbo);
+ m_instanceVbo = 0;
+ }
+ if (m_dotsVbo) {
+ glDeleteBuffers(1, &m_dotsVbo);
+ m_dotsVbo = 0;
+ }
+ if (m_dotsVao) {
+ glDeleteVertexArrays(1, &m_dotsVao);
+ m_dotsVao = 0;
+ }
- struct V {
- float x, y;
- float u, v;
- };
- V quad[6] = {
- {-1,-1, 0,0},
- { 1,-1, 1,0},
- { 1, 1, 1,1},
- {-1,-1, 0,0},
- { 1, 1, 1,1},
- {-1, 1, 0,1},
- };
+ struct V {
+ float x, y;
+ float u, v;
+ };
+ V quad[6] = {
+ {-1, -1, 0, 0}, {1, -1, 1, 0}, {1, 1, 1, 1},
+ {-1, -1, 0, 0}, {1, 1, 1, 1}, {-1, 1, 0, 1},
+ };
- glGenVertexArrays(1, &m_dotsVao);
- glBindVertexArray(m_dotsVao);
- glGenBuffers(1, &m_dotsVbo);
- glBindBuffer(GL_ARRAY_BUFFER, m_dotsVbo);
- glBufferData(GL_ARRAY_BUFFER, sizeof(quad), quad, GL_STATIC_DRAW);
+ glGenVertexArrays(1, &m_dotsVao);
+ glBindVertexArray(m_dotsVao);
+ glGenBuffers(1, &m_dotsVbo);
+ glBindBuffer(GL_ARRAY_BUFFER, m_dotsVbo);
+ glBufferData(GL_ARRAY_BUFFER, sizeof(quad), quad, GL_STATIC_DRAW);
- // attribute layout:
- // location 0: quad 顶点位置 (x,y),范围 [-1,1]
- // location 1: UV (u,v),用于 fragment shader 生成圆形
- glEnableVertexAttribArray(0);
- glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, sizeof(V), (void*)0);
- glEnableVertexAttribArray(1);
- glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, sizeof(V), (void*)(2 * sizeof(float)));
+ // attribute layout:
+ // location 0: quad 顶点位置 (x,y),范围 [-1,1]
+ // location 1: UV (u,v),用于 fragment shader 生成圆形
+ glEnableVertexAttribArray(0);
+ glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, sizeof(V), (void*)0);
+ glEnableVertexAttribArray(1);
+ glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, sizeof(V),
+ (void*)(2 * sizeof(float)));
- // instance buffer:每个点 3 个 float(offsetX, offsetZ, value)
- // layout location 2/3 对应 shader 里的 iOffsetXZ / iValue
- glGenBuffers(1, &m_instanceVbo);
- glBindBuffer(GL_ARRAY_BUFFER, m_instanceVbo);
- glBufferData(GL_ARRAY_BUFFER, sizeof(float) * 3 * qMax(1, dotCount()), nullptr, GL_DYNAMIC_DRAW);
+ // instance buffer:每个点 3 个 float(offsetX, offsetZ, value)
+ // layout location 2/3 对应 shader 里的 iOffsetXZ / iValue
+ glGenBuffers(1, &m_instanceVbo);
+ glBindBuffer(GL_ARRAY_BUFFER, m_instanceVbo);
+ glBufferData(GL_ARRAY_BUFFER, sizeof(float) * 3 * qMax(1, dotCount()),
+ nullptr, GL_DYNAMIC_DRAW);
- // location 2: vec2 iOffsetXZ(每个 instance 一次,divisor=1)
- glEnableVertexAttribArray(2);
- glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
- glVertexAttribDivisor(2, 1);
+ // location 2: vec2 iOffsetXZ(每个 instance 一次,divisor=1)
+ glEnableVertexAttribArray(2);
+ glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
+ glVertexAttribDivisor(2, 1);
- // location 3: float iValue(每个 instance 一次,divisor=1)
- glEnableVertexAttribArray(3);
- glVertexAttribPointer(3, 1, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)(2 * sizeof(float)));
- glVertexAttribDivisor(3, 1);
+ // location 3: float iValue(每个 instance 一次,divisor=1)
+ glEnableVertexAttribArray(3);
+ glVertexAttribPointer(3, 1, GL_FLOAT, GL_FALSE, 3 * sizeof(float),
+ (void*)(2 * sizeof(float)));
+ glVertexAttribDivisor(3, 1);
- glBindVertexArray(0);
+ glBindVertexArray(0);
}
void GLWidget::initDotTexture_() {
- if (m_dotTex) {
- glDeleteTextures(1, &m_dotTex);
- m_dotTex = 0;
- }
+ if (m_dotTex) {
+ glDeleteTextures(1, &m_dotTex);
+ m_dotTex = 0;
+ }
- const QString path = QStringLiteral(":/images/metal.jpeg");
- QImage img(path);
- if (img.isNull()) {
- qWarning() << "dot texture load failed:" << path;
- return;
- }
+ const QString path = QStringLiteral(":/images/metal.jpeg");
+ QImage img(path);
+ if (img.isNull()) {
+ qWarning() << "dot texture load failed:" << path;
+ return;
+ }
- QImage rgba = img.convertToFormat(QImage::Format_RGBA8888);
- glGenTextures(1, &m_dotTex);
- glBindTexture(GL_TEXTURE_2D, m_dotTex);
- glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_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_RGBA8,
- rgba.width(),
- rgba.height(),
- 0,
- GL_RGBA,
- GL_UNSIGNED_BYTE,
- rgba.constBits()
- );
- glGenerateMipmap(GL_TEXTURE_2D);
- glBindTexture(GL_TEXTURE_2D, 0);
+ QImage rgba = img.convertToFormat(QImage::Format_RGBA8888);
+ glGenTextures(1, &m_dotTex);
+ glBindTexture(GL_TEXTURE_2D, m_dotTex);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,
+ GL_LINEAR_MIPMAP_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_RGBA8, rgba.width(), rgba.height(), 0,
+ GL_RGBA, GL_UNSIGNED_BYTE, rgba.constBits());
+ glGenerateMipmap(GL_TEXTURE_2D);
+ 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;
- }
+ 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;
- };
+ 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},
+ 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},
+ // 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},
+ // 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},
+ // 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},
+ // 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;
+ // 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);
+ 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_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);
+ 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)));
+ 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);
+ 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;
- }
+ 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;
+ 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;
+ struct V {
+ float x, y, z;
+ float nx, ny, nz;
+ float u, v;
+ };
+ V verts[4] = {
+ {-hw, -hh, z, 0, 0, -1, 0, 0},
+ {+hw, -hh, z, 0, 0, -1, 1, 0},
+ {+hw, +hh, z, 0, 0, -1, 1, 1},
+ {-hw, +hh, z, 0, 0, -1, 0, 1},
+ };
+ unsigned int idx[6] = {0, 1, 2, 0, 2, 3};
+ m_baseIndexCount = 6;
- glGenVertexArrays(1, &m_baseVao);
- glBindVertexArray(m_baseVao);
+ 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_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);
+ 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)));
+ 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)));
+ glEnableVertexAttribArray(2);
+ glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, sizeof(V),
+ (void*)(6 * sizeof(float)));
- glBindVertexArray(0);
+ glBindVertexArray(0);
}
void GLWidget::initHeatmapGeometry_() {
- if (m_heatmapIbo) {
- glDeleteBuffers(1, &m_heatmapIbo);
- m_heatmapIbo = 0;
+ 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 max_res = 512;
+ const int cols = qMax(2, qMin(m_cols * m_upscale, max_res));
+ int rows = qMax(2, int(cols * aspect + 0.5f));
+ rows = qMax(2, qMin(rows, max_res));
+ m_heatmapCols = cols;
+ m_heatmapRows = rows;
+
+ const int vertex_count = cols * rows;
+ QVector 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);
}
- if (m_heatmapVbo) {
- glDeleteBuffers(1, &m_heatmapVbo);
- m_heatmapVbo = 0;
- }
- if (m_heatmapVao) {
- glDeleteVertexArrays(1, &m_heatmapVao);
- m_heatmapVao = 0;
+ }
+
+ QVector 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());
- 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;
+ glGenVertexArrays(1, &m_heatmapVao);
+ glBindVertexArray(m_heatmapVao);
- const int vertex_count = cols * rows;
- QVector verts;
- verts.reserve(vertex_count * 5);
+ glGenBuffers(1, &m_heatmapVbo);
+ glBindBuffer(GL_ARRAY_BUFFER, m_heatmapVbo);
+ glBufferData(GL_ARRAY_BUFFER, verts.size() * sizeof(float), verts.constData(),
+ GL_STATIC_DRAW);
- 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);
- }
- }
+ 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);
- QVector 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());
+ 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)));
- 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);
+ 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;
- }
+ 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();
+ m_skirtUVs.clear();
+ m_skirtNormals.clear();
- const int cols = qMax(2, m_heatmapCols);
- const int rows = qMax(2, m_heatmapRows);
+ 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);
- };
+ 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));
- }
+ 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);
+ const int border_count = m_skirtUVs.size();
+ m_skirtVertices.fill(0.0f, border_count * 2 * 8);
- QVector 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);
+ QVector 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());
+ 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);
+ 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_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);
+ 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)));
+ glEnableVertexAttribArray(0);
+ glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)0);
+ glEnableVertexAttribArray(1);
+ glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float),
+ (void*)(3 * sizeof(float)));
+ glEnableVertexAttribArray(2);
+ glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 8 * sizeof(float),
+ (void*)(6 * sizeof(float)));
- glBindVertexArray(0);
+ 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);
+ 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);
+ 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 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;
+ 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;
- }
+ if (!m_heightDirty && !m_heightValues.isEmpty()) {
+ return;
+ }
+ if (m_rows <= 0 || m_cols <= 0) {
+ return;
+ }
- QVector valuesCopy;
- {
- QMutexLocker lk(&m_dataMutex);
- valuesCopy = m_latestValues;
- }
+ QVector 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(r, c) = v;
- }
+ 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(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 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;
- }
+ 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);
+ 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(0), count * sizeof(float));
- } else {
- for (int r = 0; r < m_heightH; ++r) {
- const float* rowPtr = out.ptr(r);
- std::memcpy(m_heightValues.data() + r * m_heightW, rowPtr, m_heightW * sizeof(float));
- }
+ if (out.isContinuous()) {
+ const size_t count = size_t(m_heightW) * size_t(m_heightH);
+ std::memcpy(m_heightValues.data(), out.ptr(0),
+ count * sizeof(float));
+ } else {
+ for (int r = 0; r < m_heightH; ++r) {
+ const float* rowPtr = out.ptr(r);
+ std::memcpy(m_heightValues.data() + r * m_heightW, rowPtr,
+ m_heightW * sizeof(float));
}
- m_heightDirty = false;
+ }
+ 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;
- }
+ 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);
+ 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);
+ 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 * 0.98f);
- 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 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 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 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 topBase = topIndex * 8;
+ 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();
+ m_skirtVertices[topBase + 6] = u;
+ m_skirtVertices[topBase + 7] = v;
- 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();
- }
+ const int botBase = botIndex * 8;
+ 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();
+ m_skirtVertices[botBase + 6] = u;
+ m_skirtVertices[botBase + 7] = v;
+ }
- glBindBuffer(GL_ARRAY_BUFFER, m_skirtVbo);
- glBufferSubData(GL_ARRAY_BUFFER, 0, m_skirtVertices.size() * sizeof(float), m_skirtVertices.constData());
+ 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);
+ if (!m_baseProg || !m_baseVao || !m_heightTex || m_heightW <= 0 ||
+ m_heightH <= 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("uMinV", float(m_min));
+ m_baseProg->setUniformValue("uMaxV", float(m_max));
+ m_baseProg->setUniformValue(
+ "uTexelSize",
+ QVector2D(1.0f / float(m_heightW), 1.0f / float(m_heightH)));
+ m_baseProg->setUniformValue("uColorZero", m_colorZero);
+ m_baseProg->setUniformValue("uColorLow", m_colorLow);
+ m_baseProg->setUniformValue("uColorMid", m_colorMid);
+ m_baseProg->setUniformValue("uColorHigh", m_colorHigh);
+ m_baseProg->setUniformValue("uHeightTex", 0);
- glBindVertexArray(m_baseVao);
- glDrawElements(GL_TRIANGLES, m_baseIndexCount, GL_UNSIGNED_INT, nullptr);
- glBindVertexArray(0);
- m_baseProg->release();
+ glActiveTexture(GL_TEXTURE0);
+ glBindTexture(GL_TEXTURE_2D, m_heightTex);
+
+ glBindVertexArray(m_baseVao);
+ glDrawElements(GL_TRIANGLES, m_baseIndexCount, GL_UNSIGNED_INT, nullptr);
+ glBindVertexArray(0);
+ glBindTexture(GL_TEXTURE_2D, 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);
+ if (!m_baseProg || !m_skirtVao || m_skirtIndexCount <= 0 || !m_heightTex ||
+ m_heightW <= 0 || m_heightH <= 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("uMinV", float(m_min));
+ m_baseProg->setUniformValue("uMaxV", float(m_max));
+ m_baseProg->setUniformValue(
+ "uTexelSize",
+ QVector2D(1.0f / float(m_heightW), 1.0f / float(m_heightH)));
+ m_baseProg->setUniformValue("uColorZero", m_colorZero);
+ m_baseProg->setUniformValue("uColorLow", m_colorLow);
+ m_baseProg->setUniformValue("uColorMid", m_colorMid);
+ m_baseProg->setUniformValue("uColorHigh", m_colorHigh);
+ m_baseProg->setUniformValue("uHeightTex", 0);
- glBindVertexArray(m_skirtVao);
- glDrawElements(GL_TRIANGLES, m_skirtIndexCount, GL_UNSIGNED_INT, nullptr);
- glBindVertexArray(0);
- m_baseProg->release();
+ glActiveTexture(GL_TEXTURE0);
+ glBindTexture(GL_TEXTURE_2D, m_heightTex);
+
+ glBindVertexArray(m_skirtVao);
+ glDrawElements(GL_TRIANGLES, m_skirtIndexCount, GL_UNSIGNED_INT, nullptr);
+ glBindVertexArray(0);
+ glBindTexture(GL_TEXTURE_2D, 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);
+ 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 * 0.98f));
+ 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("uColorZero", m_colorZero);
+ 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);
+ glActiveTexture(GL_TEXTURE0);
+ glBindTexture(GL_TEXTURE_2D, m_heightTex);
- glBindVertexArray(m_heatmapVao);
- glDrawElements(GL_TRIANGLES, m_heatmapIndexCount, GL_UNSIGNED_INT, nullptr);
- glBindVertexArray(0);
+ glBindVertexArray(m_heatmapVao);
+ glDrawElements(GL_TRIANGLES, m_heatmapIndexCount, GL_UNSIGNED_INT, nullptr);
+ glBindVertexArray(0);
- glBindTexture(GL_TEXTURE_2D, 0);
- m_heatmapProg->release();
+ 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");
- const QString fsd_path = QStringLiteral(":/shaders/dots.frag");
- const QString vsb_path = QStringLiteral(":/shaders/bg.vert");
- 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");
- 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");
+ // Qt Resource 里打包的 shader 文件路径:使用 `:/` 前缀(不是文件系统路径)
+ const QString vsd_path = QStringLiteral(":/shaders/dots.vert");
+ const QString fsd_path = QStringLiteral(":/shaders/dots.frag");
+ const QString vsb_path = QStringLiteral(":/shaders/bg.vert");
+ 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");
+ 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;
- const bool okV = p->addShaderFromSourceCode(QOpenGLShader::Vertex, readFile(vsb_path));
- const bool okF = p->addShaderFromSourceCode(QOpenGLShader::Fragment, readFile(fsb_path));
- const bool okL = okV && okF && p->link();
- if (!okL) {
- qWarning() << "bg program build failed:" << vsb_path << fsb_path << p->log();
- delete p;
- p = nullptr;
- }
- m_bgProg = p;
+ {
+ auto* p = new QOpenGLShaderProgram;
+ const bool okV =
+ p->addShaderFromSourceCode(QOpenGLShader::Vertex, readFile(vsb_path));
+ const bool okF =
+ p->addShaderFromSourceCode(QOpenGLShader::Fragment, readFile(fsb_path));
+ const bool okL = okV && okF && p->link();
+ if (!okL) {
+ qWarning() << "bg program build failed:" << vsb_path << fsb_path
+ << p->log();
+ delete p;
+ p = nullptr;
}
- {
- auto *p = new QOpenGLShaderProgram;
- const bool okV = p->addShaderFromSourceCode(QOpenGLShader::Vertex, readFile(vsp_path));
- const bool okF = p->addShaderFromSourceCode(QOpenGLShader::Fragment, readFile(fsp_path));
- const bool okL = okV && okF && p->link();
- if (!okL) {
- qWarning() << "panel program build failed:" << vsp_path << fsp_path << p->log();
- delete p;
- p = nullptr;
- }
- m_panelProg = p;
+ m_bgProg = p;
+ }
+ {
+ auto* p = new QOpenGLShaderProgram;
+ const bool okV =
+ p->addShaderFromSourceCode(QOpenGLShader::Vertex, readFile(vsp_path));
+ const bool okF =
+ p->addShaderFromSourceCode(QOpenGLShader::Fragment, readFile(fsp_path));
+ const bool okL = okV && okF && p->link();
+ if (!okL) {
+ qWarning() << "panel program build failed:" << vsp_path << fsp_path
+ << p->log();
+ delete p;
+ p = nullptr;
}
- {
- auto *p = new QOpenGLShaderProgram;
- const bool okV = p->addShaderFromSourceCode(QOpenGLShader::Vertex, readFile(vsd_path));
- const bool okF = p->addShaderFromSourceCode(QOpenGLShader::Fragment, readFile(fsd_path));
- const bool okL = okV && okF && p->link();
- if (!okL) {
- qWarning() << "dots program build failed:" << vsd_path << fsd_path << p->log();
- delete p;
- p = nullptr;
- }
- m_dotsProg = p;
+ m_panelProg = p;
+ }
+ {
+ auto* p = new QOpenGLShaderProgram;
+ const bool okV =
+ p->addShaderFromSourceCode(QOpenGLShader::Vertex, readFile(vsd_path));
+ const bool okF =
+ p->addShaderFromSourceCode(QOpenGLShader::Fragment, readFile(fsd_path));
+ const bool okL = okV && okF && p->link();
+ if (!okL) {
+ qWarning() << "dots program build failed:" << vsd_path << fsd_path
+ << p->log();
+ delete p;
+ p = nullptr;
}
- {
- 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;
+ 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;
}
- {
- 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;
+ 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;
}
- {
- 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;
+ 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_() {
- if (dotCount() <= 0) {
- m_instanceCount = 0;
- return;
+ if (dotCount() <= 0) {
+ m_instanceCount = 0;
+ return;
+ }
+
+ QVector valuesCopy;
+ bool dirty = false;
+ {
+ QMutexLocker lk(&m_dataMutex);
+ dirty = m_valuesDirty;
+ if (dirty) {
+ valuesCopy = m_latestValues;
+ m_valuesDirty = false;
}
+ }
+ if (!dirty) return;
- QVector valuesCopy;
- bool dirty = false;
- {
- QMutexLocker lk(&m_dataMutex);
- dirty = m_valuesDirty;
- if (dirty) {
- valuesCopy = m_latestValues;
- m_valuesDirty = false;
- }
- }
- if (!dirty)
- return;
+ const int n = dotCount();
+ m_instanceCount = n;
+ QVector inst;
+ // 每个点 3 个 float:offsetX, offsetZ, value(对应 dots.vert: iOffsetXZ +
+ // iValue)
+ inst.resize(n * 3);
- const int n = dotCount();
- m_instanceCount = n;
- QVector inst;
- // 每个点 3 个 float:offsetX, offsetZ, value(对应 dots.vert: iOffsetXZ + iValue)
- inst.resize(n * 3);
+ const float w = (m_cols - 1) * m_pitch;
+ const float h = (m_rows - 1) * m_pitch;
- const float w = (m_cols - 1) * m_pitch;
- const float h = (m_rows - 1) * m_pitch;
+ // 把点阵居中到原点附近(x/z 以中心对称)
+ for (int i = 0; i < n; ++i) {
+ const int r = (m_cols > 0) ? (i / m_cols) : 0;
+ const int c = (m_cols > 0) ? (i % m_cols) : 0;
- // 把点阵居中到原点附近(x/z 以中心对称)
- for (int i = 0; i < n; ++i) {
- const int r = (m_cols > 0) ? (i / m_cols) : 0;
- const int c = (m_cols > 0) ? (i % m_cols) : 0;
+ const float x = (c * m_pitch) - w * 0.5f;
+ const float z = (r * m_pitch) - h * 0.5f;
- const float x = (c * m_pitch) - w * 0.5f;
- const float z = (r * m_pitch) - h * 0.5f;
+ inst[i * 3 + 0] = x;
+ inst[i * 3 + 1] = z;
+ inst[i * 3 + 2] = (i < valuesCopy.size()) ? valuesCopy[i] : m_min;
+ }
- inst[i * 3 + 0] = x;
- inst[i * 3 + 1] = z;
- inst[i * 3 + 2] = (i < valuesCopy.size()) ? valuesCopy[i] : m_min;
- }
-
- // 把 CPU 生成的 instance 数据上传到 GPU(后续 draw 时由 VAO 里的 attribute 2/3 读取)
- glBindBuffer(GL_ARRAY_BUFFER, m_instanceVbo);
- glBufferSubData(GL_ARRAY_BUFFER, 0, inst.size() * sizeof(float), inst.constData());
+ // 把 CPU 生成的 instance 数据上传到 GPU(后续 draw 时由 VAO 里的 attribute
+ // 2/3 读取)
+ glBindBuffer(GL_ARRAY_BUFFER, m_instanceVbo);
+ glBufferSubData(GL_ARRAY_BUFFER, 0, inst.size() * sizeof(float),
+ inst.constData());
}
void GLWidget::updateMatrices_() {
- // MVP = Projection * View * Model
- //
- // OpenGL 的顶点最终要写到 gl_Position(裁剪空间),所以我们需要一个矩阵把“世界坐标”变成“屏幕可见”的坐标。
- // 这里为了入门简单:
- // - panel/dots 顶点数据直接当作世界坐标(Model=Identity)
- // - View 用 lookAt 放一个相机
- // - Projection 用透视投影(perspective)
- const int w = width();
- const int h = height();
- if (w <= 0 || h <= 0) {
- m_mvp.setToIdentity();
- return;
- }
+ // MVP = Projection * View * Model
+ //
+ // OpenGL 的顶点最终要写到
+ // gl_Position(裁剪空间),所以我们需要一个矩阵把“世界坐标”变成“屏幕可见”的坐标。
+ // 这里为了入门简单:
+ // - panel/dots 顶点数据直接当作世界坐标(Model=Identity)
+ // - View 用 lookAt 放一个相机
+ // - Projection 用透视投影(perspective)
+ const int w = width();
+ const int h = height();
+ if (w <= 0 || h <= 0) {
+ m_mvp.setToIdentity();
+ return;
+ }
- const float aspect = float(w) / float(h);
- const float radius = 0.5f * qSqrt(m_panelW * m_panelW + m_panelD * m_panelD);
- const float distance = qMax(0.5f, radius * 2.5f);
+ const float aspect = float(w) / float(h);
+ const float radius = 0.5f * qSqrt(m_panelW * m_panelW + m_panelD * m_panelD);
+ const float distance = qMax(0.5f, radius * 2.5f);
- // 让相机看向 panel 的中心点
- const QVector3D center(0.0f, 0.0f, 0.0f);
+ // 让相机看向 panel 的中心点
+ const QVector3D center(0.0f, 0.0f, 0.0f);
- // yaw/pitch 控制相机绕目标点“环绕”(orbit camera)
- const float yawRad = qDegreesToRadians(m_camYawDeg);
- const float pitchRad = qDegreesToRadians(m_camPitchDeg);
- const float cosPitch = qCos(pitchRad);
- const float sinPitch = qSin(pitchRad);
+ // yaw/pitch 控制相机绕目标点“环绕”(orbit camera)
+ const float yawRad = qDegreesToRadians(m_camYawDeg);
+ const float pitchRad = qDegreesToRadians(m_camPitchDeg);
+ const float cosPitch = qCos(pitchRad);
+ const float sinPitch = qSin(pitchRad);
- const QVector3D eye = center + QVector3D(
- distance * cosPitch * qCos(yawRad),
- distance * sinPitch,
- distance * cosPitch * qSin(yawRad)
- );
- m_cameraPos = eye;
+ const QVector3D eye = center + QVector3D(distance * cosPitch * qCos(yawRad),
+ distance * sinPitch,
+ distance * cosPitch * qSin(yawRad));
+ m_cameraPos = eye;
- QMatrix4x4 view;
- view.lookAt(eye, center, QVector3D(0.0f, 1.0f, 0.0f));
+ QMatrix4x4 view;
+ view.lookAt(eye, center, QVector3D(0.0f, 1.0f, 0.0f));
- QMatrix4x4 proj;
- // fov=45°,near=0.01,far=足够大(保证 panel + dots 在裁剪范围内)
- proj.perspective(m_zoom_, aspect, 0.01f, qMax(10.0f, distance * 10.0f));
+ QMatrix4x4 proj;
+ // fov=45°,near=0.01,far=足够大(保证 panel + dots 在裁剪范围内)
+ proj.perspective(m_zoom_, aspect, 0.01f, qMax(10.0f, distance * 10.0f));
- m_mvp = proj * view;
+ m_mvp = proj * view;
}
-void GLWidget::updateRoom_(){
- if (!m_roomProg || !room_vao || room_index_count <= 0) {
- return;
- }
+void GLWidget::updateRoom_() {
+ if (!m_roomProg || !room_vao || room_index_count <= 0) {
+ return;
+ }
- m_roomProg->bind();
+ 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 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;
+ 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);
+ 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();
+ 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;
+ if (m_showGrid == on) return;
- m_showGrid = on;
- update();
+ m_showGrid = on;
+ update();
+}
+
+void GLWidget::setUseHeatmap(bool on) {
+ if (m_useHeatmap == on) return;
+ m_useHeatmap = on;
+ update();
}
void GLWidget::setRow(int row) {
- row = qMax(0, row);
- if (m_rows == row)
- return;
- setSpec(row, m_cols, m_pitch, m_dotRadius);
+ row = qMax(0, row);
+ if (m_rows == row) return;
+ setSpec(row, m_cols, m_pitch, m_dotRadius);
}
void GLWidget::setCol(int col) {
- col = qMax(0, col);
- if (m_cols == col)
- return;
- setSpec(m_rows, col, m_pitch, m_dotRadius);
+ col = qMax(0, col);
+ if (m_cols == col) return;
+ setSpec(m_rows, col, m_pitch, m_dotRadius);
}
diff --git a/src/serial/serial_backend.cpp b/src/serial/serial_backend.cpp
index ae23b3d..55a39c9 100644
--- a/src/serial/serial_backend.cpp
+++ b/src/serial/serial_backend.cpp
@@ -1,9 +1,11 @@
#include "serial_backend.h"
-#include "piezoresistive_a_protocol.h"
#include "serial_qt_transport.h"
#include
+#include
+#include
+#include
#include
#include
#include
@@ -14,13 +16,20 @@ SerialBackend::SerialBackend(QObject *parent)
: QObject(parent), m_packetQueue(2048), m_frameQueue(2048), m_readThread(&m_packetQueue), m_decodeThread(&m_packetQueue, &m_frameQueue)
{
m_request.dataLength = 24;
- m_spec.model = QStringLiteral("PZR-A");
- auto codec = std::make_shared();
- auto decoder = std::make_shared();
- auto format = std::make_shared();
- m_manager.registerProtocol(codec->name(), {codec, decoder, format});
- m_manager.setActiveProtocol(codec->name());
+ const QString pluginDir = QDir(QCoreApplication::applicationDirPath())
+ .filePath(QStringLiteral("plugins/decoders"));
+ QStringList errors;
+ const QStringList loaded = m_manager.loadPlugins(pluginDir, &errors);
+ for (const QString& err : errors) {
+ qWarning().noquote() << err;
+ }
+ if (!loaded.isEmpty()) {
+ m_spec.model = m_manager.activeProtocol();
+ } else {
+ qWarning() << "No protocol plugins loaded.";
+ m_spec.model = QStringLiteral("UNKNOWN");
+ }
m_sendWorker = new SerialSendWorker();
m_sendWorker->moveToThread(&m_sendThread);
@@ -175,6 +184,10 @@ void SerialBackend::setProtocol(const QString &name)
if (!m_manager.setActiveProtocol(name))
return;
updateProtocolBindings_();
+ if (m_spec.model != name) {
+ m_spec.model = name;
+ emit sensorModelChanged();
+ }
emit protocolChanged();
}
diff --git a/src/serial/serial_manager.cpp b/src/serial/serial_manager.cpp
index 53ea384..738895e 100644
--- a/src/serial/serial_manager.cpp
+++ b/src/serial/serial_manager.cpp
@@ -1,4 +1,8 @@
#include "serial_manager.h"
+#include "protocol_plugin.h"
+
+#include
+#include
void SerialManager::registerProtocol(const QString& name, const ProtocolBundle& bundle) {
if (name.isEmpty())
@@ -15,8 +19,76 @@ bool SerialManager::setActiveProtocol(const QString& name) {
return true;
}
-SerialManager::ProtocolBundle SerialManager::activeBundle() const {
+ProtocolBundle SerialManager::activeBundle() const {
if (!m_protocols.contains(m_activeName))
return {};
return m_protocols.value(m_activeName);
}
+
+QStringList SerialManager::loadPlugins(const QString& dirPath, QStringList* errors) {
+ QStringList loaded;
+ if (!m_pluginLoaders.empty()) {
+ return loaded;
+ }
+
+ QDir dir(dirPath);
+ if (!dir.exists()) {
+ if (errors)
+ errors->append(QStringLiteral("Plugin dir not found: %1").arg(dirPath));
+ return loaded;
+ }
+
+ QStringList filters;
+#if defined(Q_OS_WIN)
+ filters << QStringLiteral("*.dll");
+#elif defined(Q_OS_MAC)
+ filters << QStringLiteral("*.dylib");
+#else
+ filters << QStringLiteral("*.so");
+#endif
+
+ const QStringList entries = dir.entryList(filters, QDir::Files);
+ for (const QString& fileName : entries) {
+ const QString path = dir.absoluteFilePath(fileName);
+ auto loader = std::make_unique(path);
+ QObject* instance = loader->instance();
+ if (!instance) {
+ if (errors)
+ errors->append(QStringLiteral("Load failed: %1 (%2)").arg(path, loader->errorString()));
+ continue;
+ }
+
+ auto* plugin = qobject_cast(instance);
+ if (!plugin) {
+ if (errors)
+ errors->append(QStringLiteral("Invalid plugin: %1").arg(path));
+ loader->unload();
+ continue;
+ }
+ if (plugin->apiVersion() != kProtocolPluginApiVersion) {
+ if (errors)
+ errors->append(QStringLiteral("API mismatch: %1").arg(path));
+ loader->unload();
+ continue;
+ }
+
+ const ProtocolBundle bundle = plugin->createBundle();
+ if (!bundle.codec || !bundle.decoder || !bundle.format) {
+ if (errors)
+ errors->append(QStringLiteral("Missing components: %1").arg(path));
+ loader->unload();
+ continue;
+ }
+
+ const QString name = plugin->protocolName();
+ if (!m_protocols.contains(name)) {
+ registerProtocol(name, bundle);
+ loaded.append(name);
+ m_pluginLoaders.push_back(std::move(loader));
+ } else {
+ loader->unload();
+ }
+ }
+
+ return loaded;
+}
diff --git a/src/serial/serial_manager.h b/src/serial/serial_manager.h
index e8a8f8c..2d70d7a 100644
--- a/src/serial/serial_manager.h
+++ b/src/serial/serial_manager.h
@@ -3,22 +3,18 @@
#include
#include
+#include
+#include
+#include
#include
-#include "serial_codec.h"
-#include "serial_decoder.h"
-#include "serial_format.h"
+#include "protocol_bundle.h"
class SerialManager {
public:
- struct ProtocolBundle {
- std::shared_ptr codec;
- std::shared_ptr decoder;
- std::shared_ptr format;
- };
-
void registerProtocol(const QString& name, const ProtocolBundle& bundle);
bool setActiveProtocol(const QString& name);
+ QStringList loadPlugins(const QString& dirPath, QStringList* errors = nullptr);
QString activeProtocol() const { return m_activeName; }
ProtocolBundle activeBundle() const;
@@ -26,6 +22,7 @@ public:
private:
QHash m_protocols;
QString m_activeName;
+ std::vector> m_pluginLoaders;
};
#endif // TACTILEIPC3D_SERIAL_MANAGER_H