Skip to main content
标签ad报错:该广告ID(9)不存在。
  主页 > Qt入门

一个Qt/C++ Demo,用于实现一个用于显示3D瀑布图的控件效果

2023-04-23 浏览:
标签ad报错:该广告ID(7)不存在。
#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函数中,我们构造了所有流星的顶点和纹理坐标,
并绘制出瀑布效果。我们还通过鼠标拖动事件处理函数,通过鼠标拖动来控制频谱的旋转,从而使频谱的变化更加立体丰富。