#pragma once
#include <QWidget>
#include <QHBoxLayout>
#include <QLabel>
#include <QPixmap>
#include <QPainter>
#include <QTimer>
#include <fftw3.h>
#include <QOpenGLWidget>
#include <QOpenGLFunctions>
#include <QVector>
class WaterfallWidget : public QOpenGLWidget, protected QOpenGLFunctions {
Q_OBJECT
public:
explicit WaterfallWidget(QWidget* parent = nullptr) :
QOpenGLWidget(parent),
m_xRot(-90.0f),
m_yRot(0.0f),
m_zRot(0.0f),
m_xDelta(-0.1f),
m_scrollSpeed(0.01f),
m_timer(new QTimer(this)),
m_spectrumDataBuffer(300, QVector<double>(1000, 0))
{
// 频谱计算计划
m_plan = fftw_plan_r2r_1d(m_spectrumDataBuffer[0].size(), m_spectrumDataBuffer[0].data(), m_spectrumDataBuffer[0].data(), FFTW_R2HC, FFTW_ESTIMATE);
}
~WaterfallWidget() override {
delete m_timer;
fftw_destroy_plan(m_plan);
}
void start() {
m_timer->start(50);
}
void stop() {
m_timer->stop();
}
protected:
void initializeGL() override {
initializeOpenGLFunctions();
glEnable(GL_DEPTH_TEST);
glShadeModel(GL_SMOOTH);
glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
glClearDepth(1.0f);
glFrontFace(GL_CW);
glCullFace(GL_BACK);
glEnable(GL_CULL_FACE);
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
}
void resizeGL(int w, int h) override {
glViewport(0, 0, w, h);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
gluPerspective(45.0f, static_cast<GLfloat>(w)/static_cast<GLfloat>(h), 0.1f, 100.0f);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
}
void paintGL() override {
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glLoadIdentity();
// 绘制所有的射线
for (int i = 0; i < m_spectrumDataBuffer.size(); ++i) {
glPushMatrix();
glTranslatef(0.0f, 0.0f, i * m_xDelta);
drawRay(i);
glPopMatrix();
}
// 滚动所有射线
for (int i = 0; i < m_spectrumDataBuffer.size(); ++i) {
for (int j = 0; j < m_spectrumDataBuffer[i].size(); ++j) {
m_spectrumDataBuffer[i][j] += m_scrollSpeed;
if (m_spectrumDataBuffer[i][j] > 1.0) {
m_spectrumDataBuffer[i][j] -= 1.0;
}
}
}
}
private:
void onTimeout() {
// 更新数据
for (int i = 1; i < m_spectrumDataBuffer.size(); ++i) {
m_spectrumDataBuffer[i - 1] = m_spectrumDataBuffer[i];
}
QVector<double> newData(m_spectrumDataBuffer.last().size(), 0.0);
for (int i = 0; i < newData.size(); ++i) {
newData[i] = static_cast<double>(rand()) / RAND_MAX;
}
m_spectrumDataBuffer[m_spectrumDataBuffer.size() - 1] = newData;
update();
}
void drawRay(int n) {
QVector<QVector3D> vertices;
QVector<QVector2D> texCoords;
// 构造三角形条带
for (int i = 0; i < m_spectrumDataBuffer[n].size() - 1; ++i) {
vertices.append(QVector3D(i, 0.0f, m_spectrumDataBuffer[n][i]));
vertices.append(QVector3D(i, 1.0f, m_spectrumDataBuffer[n][i]));
texCoords.append(QVector2D(static_cast<GLfloat>(i), 1.0f));
texCoords.append(QVector2D(static_cast<GLfloat>(i), 0.0f));
}
// 绘制三角形条带
glEnableClientState(GL_VERTEX_ARRAY);
glEnableClientState(GL_TEXTURE_COORD_ARRAY);
glVertexPointer(3, GL_FLOAT, sizeof(QVector3D), vertices.constData());
glTexCoordPointer(2, GL_FLOAT, sizeof(QVector2D), texCoords.constData());
glDrawArrays(GL_TRIANGLE_STRIP, 0, vertices.size());
glDisableClientState(GL_VERTEX_ARRAY);
glDisableClientState(GL_TEXTURE_COORD_ARRAY);
}
void mousePressEvent(QMouseEvent* event) override {
m_lastPos = event->pos();
}
void mouseMoveEvent(QMouseEvent* event) override {
int dx = event->x() - m_lastPos.x();
int dy = event->y() - m_lastPos.y();
if (event->buttons() & Qt::LeftButton) {
m_xRot += dy;
m_yRot += dx;
} else if (event->buttons() & Qt::RightButton) {
m_xRot += dy;
m_zRot += dx;
}
m_lastPos = event->pos();
update();
}
private:
fftw_plan m_plan;
int m_xRot, m_yRot, m_zRot;
QPoint m_lastPos;
float m_xDelta, m_scrollSpeed;
QTimer* m_timer;
QVector<QVector<double>> m_spectrumDataBuffer;
};
该Demo继承自QOpenGLWidget,实现了一个用于显示3D瀑布图的控件。
在该控件中,使用QVector来保存频谱图中每一点的数据以及用于计算频谱的原始数据。
使用QOpenGLWidget来绘制瀑布图,并通过鼠标事件来控制瀑布的旋转。
在绘制瀑布图时,我们使用OpenGL的三角形条带来表达流星,可以使用纹理来控制流星的样式和颜色。
代码中,我们通过调用QOpenGLFunctions类中的函数来初始化OpenGL环境,并实现了三个OpenGL事件函数,
分别是initializeGL、resizeGL和paintGL。在paintGL函数中,我们构造了所有流星的顶点和纹理坐标,
并绘制出瀑布效果。我们还通过鼠标拖动事件处理函数,通过鼠标拖动来控制频谱的旋转,从而使频谱的变化更加立体丰富。