Qt使用QPainter绘制3D立方体

本文实例为大家分享了使用QPainter绘制3D立方体的具体代码,供大家参考,具体内容如下

1.实现思路

(网上有另一篇类似的,不过他不是用的 Qt 自带的矩阵运算类)

实现思路有点类似使用 OpenGL 画立方体,先准备顶点数据:

//立方体前后四个顶点,从右上角开始顺时针vertexArr=QVector<QVector3D>{ QVector3D{1,1,1}, QVector3D{1,-1,1}, QVector3D{-1,-1,1}, QVector3D{-1,1,1},  QVector3D{1,1,-1}, QVector3D{1,-1,-1}, QVector3D{-1,-1,-1}, QVector3D{-1,1,-1} };  //六个面,一个面包含四个顶点 elementArr=QVector<QVector<int>>{ {0,1,2,3}, {4,5,6,7}, {0,4,5,1}, {1,5,6,2}, {2,6,7,3}, {3,7,4,0} };

然后再和旋转矩阵、透视矩阵进行运算,得到 3D 顶点坐标在 2D 平面上的 xy 值。根据顶点 xy 值,得到每个面的路径,然后绘制表面的路径。

这里面比较麻烦的是判断哪些是表面,单个立方体还好,可以遍历比较 z 值,如果是多个物体运算量就大了,还是直接 OpenGL 吧,毕竟我这个只是画着玩的。

2.实现代码

代码 github 链接

实现效果 GIF 动图:

主要代码:

#ifndef MYCUBE_H#define MYCUBE_H #include <QWidget>#include <QMouseEvent> #include <QVector3D>#include <QMatrix4x4> class MyCube : public QWidget{  Q_OBJECTpublic:  explicit MyCube(QWidget *parent = nullptr); protected:  void paintEvent(QPaintEvent *event) override;  void mousePressEvent(QMouseEvent *event) override;  void mouseMoveEvent(QMouseEvent *event) override;  void mouseReleaseEvent(QMouseEvent *event) override;   QPointF getPoint(const QVector3D &vt,int w) const; private:  QVector<QVector3D> vertexArr;   //八个顶点  QVector<QVector<int>> elementArr; //六个面  QMatrix4x4 rotateMat;   //旋转矩阵  QPoint mousePos;      //鼠标位置  bool mousePressed=false;  //鼠标按下标志位}; #endif // MYCUBE_H
#include "MyCube.h" #include <QPainter>#include <QtMath>#include <QDebug> MyCube::MyCube(QWidget *parent)  : QWidget(parent){  //     7------------------4  //    /         / |  //   3------------------0  |  //   |         |  |  //   |         |  |  //   |         |  |  //   |         |  |  //   |  6       |  5  //   |         | /  //   2------------------1  //立方体前后四个顶点,从右上角开始顺时针  vertexArr=QVector<QVector3D>{      QVector3D{1,1,1},      QVector3D{1,-1,1},      QVector3D{-1,-1,1},      QVector3D{-1,1,1},       QVector3D{1,1,-1},      QVector3D{1,-1,-1},      QVector3D{-1,-1,-1},      QVector3D{-1,1,-1} };   //六个面,一个面包含四个顶点  elementArr=QVector<QVector<int>>{  {0,1,2,3},  {4,5,6,7},  {0,4,5,1},  {1,5,6,2},  {2,6,7,3},  {3,7,4,0} };   setFocusPolicy(Qt::ClickFocus); //Widget默认没有焦点} void MyCube::paintEvent(QPaintEvent *event){  Q_UNUSED(event)  QPainter painter(this);  //先画一个白底黑框  painter.fillRect(this->rect(),Qt::white);  QPen pen(Qt::black);  painter.setPen(pen);  painter.drawRect(this->rect().adjusted(0,0,-1,-1)); //右下角会超出范围   //思路,找到z值最高的顶点,然后绘制该顶点相邻的面  // 根据z值计算,近大远小  //(此外,Qt是屏幕坐标系,原点在左上角)   //矩形边框参考大小  const int cube_width=(width()>height()?height():width())/4;   //投影矩阵  //(奇怪,为什么只是平移了z轴,没用perspective函数就有远小近大的效果,  //在我的想象中默认不该是正交投影么)  QMatrix4x4 perspective_mat;  perspective_mat.translate(0.0f,0.0f,-0.1f);   //计算顶点变换后坐标,包含z值max点就是正交表面可见的,  //再计算下远小近大的透视投影效果齐活了  QList<QVector3D> vertex_list; //和矩阵运算后的顶点  QList<int> vertex_max_list; //top顶点在arr的位置  float vertex_max_value;   //top值  //根据旋转矩阵计算每个顶点  for(int i=0;i<vertexArr.count();i++)  {    QVector3D vertex=vertexArr.at(i)*rotateMat*perspective_mat;    vertex_list.push_back(vertex);    //找出z值max的顶点    if(i==0){      vertex_max_list.push_back(0);      vertex_max_value=vertex.z();    }else{      if(vertex.z()>vertex_max_value){        vertex_max_list.clear();        vertex_max_list.push_back(i);        vertex_max_value=vertex.z();      }else if(abs(vertex.z()-vertex_max_value)<(1E-7)){        vertex_max_list.push_back(i);      }    }  }   //把原点移到中间来  painter.save();  painter.translate(width()/2,height()/2);  //绘制front和back六个面,先计算路径再绘制  QList<QPainterPath> element_path_list; //每个面路径  QList<float> element_z_values;  //每个面中心点的z值  QList<QPointF> element_z_points; //每个面中心点在平面对应xy值  QList<int> element_front_list;  //elementArr中表面的index  for(int i=0;i<elementArr.count();i++)  {     const QVector3D vt0=vertex_list.at(elementArr.at(i).at(0));    const QVector3D vt1=vertex_list.at(elementArr.at(i).at(1));    const QVector3D vt2=vertex_list.at(elementArr.at(i).at(2));    const QVector3D vt3=vertex_list.at(elementArr.at(i).at(3));     //单个面的路径    QPainterPath element_path;    element_path.moveTo(getPoint(vt0,cube_width));    element_path.lineTo(getPoint(vt1,cube_width));    element_path.lineTo(getPoint(vt2,cube_width));    element_path.lineTo(getPoint(vt3,cube_width));    element_path.closeSubpath();     //包含zmax点的就是正交表面可见的    bool is_front=true;    for(int vertex_index:vertex_max_list){      if(!elementArr.at(i).contains(vertex_index)){        is_front=false;        break;      }    }    if(is_front){      element_front_list.push_back(i);    }    element_path_list.push_back(element_path);    element_z_values.push_back((vt0.z()+vt2.z())/2);    element_z_points.push_back((getPoint(vt0,cube_width)+getPoint(vt2,cube_width))/2);  }   //远小近大,还要把包含max但是被近大遮盖的去掉  QList<int> element_front_remove;  for(int i=0;i<element_front_list.count();i++)  {    for(int j=0;j<element_front_list.count();j++)    {      if(i==j)        continue;      const int index_i=element_front_list.at(i);      const int index_j=element_front_list.at(j);      if(element_z_values.at(index_i)>element_z_values.at(index_j)          &&element_path_list.at(index_i).contains(element_z_points.at(index_j))){        element_front_remove.push_back(index_j);      }    }  }  for(int index:element_front_remove){    element_front_list.removeOne(index);  }   //根据计算好的路径绘制  painter.setRenderHint(QPainter::Antialiasing,true);  //画表面  for(auto index:element_front_list){    painter.fillPath(element_path_list.at(index),Qt::green);  }  //画被遮盖面的边框虚线  painter.setPen(QPen(Qt::white,1,Qt::DashLine));  for(int i=0;i<element_path_list.count();i++){    if(element_front_list.contains(i))      continue;    painter.drawPath(element_path_list.at(i));  }  //画表面边框  painter.setPen(QPen(Qt::black,2));  for(auto index:element_front_list){    painter.drawPath(element_path_list.at(index));  }  painter.restore();   painter.drawText(20,30,"Drag Moving");} void MyCube::mousePressEvent(QMouseEvent *event){  mousePressed=true;  mousePos=event->pos();  QWidget::mousePressEvent(event);} void MyCube::mouseMoveEvent(QMouseEvent *event){  if(mousePressed){    const QPoint posOffset=event->pos()-mousePos;    mousePos=event->pos();    //旋转矩阵 x和y分量    //rotateMat.rotate(posOffset.x(),QVector3D(0.0f,-0.5f,0.0f));    //rotateMat.rotate(posOffset.y(),QVector3D(0.5f,0.0f,0.0f));    rotateMat.rotate(1.1f,QVector3D(0.5f*posOffset.y(),-0.5f*posOffset.x(),0.0f));    update();  }  QWidget::mouseMoveEvent(event);} void MyCube::mouseReleaseEvent(QMouseEvent *event){  mousePressed=false;  QWidget::mouseReleaseEvent(event);} QPointF MyCube::getPoint(const QVector3D &vt,int w) const{  //可以用z来手动计算远小近大,也可以矩阵运算  //const float z_offset=vt.z()*0.1;  //return QPointF{ vt.x()*w*(1+z_offset), vt.y()*w*(1+z_offset) };  return QPointF{ vt.x()*w, vt.y()*w };}

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。

也就越容易失败,还不如怀揣一颗平常心,

Qt使用QPainter绘制3D立方体

相关文章:

你感兴趣的文章:

标签云: