Qt使用QPainter繪制3D立方體
本文實(shí)例為大家分享了使用QPainter繪制3D立方體的具體代碼,供大家參考,具體內(nèi)容如下
1.實(shí)現(xiàn)思路
(網(wǎng)上有另一篇類似的,不過他不是用的 Qt 自帶的矩陣運(yùn)算類)
實(shí)現(xiàn)思路有點(diǎn)類似使用 OpenGL 畫立方體,先準(zhǔn)備頂點(diǎn)數(shù)據(jù):
//立方體前后四個(gè)頂點(diǎn),從右上角開始順時(shí)針 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} }; //六個(gè)面,一個(gè)面包含四個(gè)頂點(diǎn) 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} };
然后再和旋轉(zhuǎn)矩陣、透視矩陣進(jìn)行運(yùn)算,得到 3D 頂點(diǎn)坐標(biāo)在 2D 平面上的 xy 值。根據(jù)頂點(diǎn) xy 值,得到每個(gè)面的路徑,然后繪制表面的路徑。
這里面比較麻煩的是判斷哪些是表面,單個(gè)立方體還好,可以遍歷比較 z 值,如果是多個(gè)物體運(yùn)算量就大了,還是直接 OpenGL 吧,畢竟我這個(gè)只是畫著玩的。
2.實(shí)現(xiàn)代碼
代碼 github 鏈接
實(shí)現(xiàn)效果 GIF 動(dòng)圖:
主要代碼:
#ifndef MYCUBE_H #define MYCUBE_H #include <QWidget> #include <QMouseEvent> #include <QVector3D> #include <QMatrix4x4> class MyCube : public QWidget { Q_OBJECT public: 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; //八個(gè)頂點(diǎn) QVector<QVector<int>> elementArr; //六個(gè)面 QMatrix4x4 rotateMat; //旋轉(zhuǎn)矩陣 QPoint mousePos; //鼠標(biāo)位置 bool mousePressed=false; //鼠標(biāo)按下標(biāo)志位 }; #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 //立方體前后四個(gè)頂點(diǎn),從右上角開始順時(shí)針 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} }; //六個(gè)面,一個(gè)面包含四個(gè)頂點(diǎn) 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默認(rèn)沒有焦點(diǎn) } void MyCube::paintEvent(QPaintEvent *event) { Q_UNUSED(event) QPainter painter(this); //先畫一個(gè)白底黑框 painter.fillRect(this->rect(),Qt::white); QPen pen(Qt::black); painter.setPen(pen); painter.drawRect(this->rect().adjusted(0,0,-1,-1)); //右下角會(huì)超出范圍 //思路,找到z值最高的頂點(diǎn),然后繪制該頂點(diǎn)相鄰的面 // 根據(jù)z值計(jì)算,近大遠(yuǎn)小 //(此外,Qt是屏幕坐標(biāo)系,原點(diǎn)在左上角) //矩形邊框參考大小 const int cube_width=(width()>height()?height():width())/4; //投影矩陣 //(奇怪,為什么只是平移了z軸,沒用perspective函數(shù)就有遠(yuǎn)小近大的效果, //在我的想象中默認(rèn)不該是正交投影么) QMatrix4x4 perspective_mat; perspective_mat.translate(0.0f,0.0f,-0.1f); //計(jì)算頂點(diǎn)變換后坐標(biāo),包含z值max點(diǎn)就是正交表面可見的, //再計(jì)算下遠(yuǎn)小近大的透視投影效果齊活了 QList<QVector3D> vertex_list; //和矩陣運(yùn)算后的頂點(diǎn) QList<int> vertex_max_list; //top頂點(diǎn)在arr的位置 float vertex_max_value; //top值 //根據(jù)旋轉(zhuǎn)矩陣計(jì)算每個(gè)頂點(diǎn) for(int i=0;i<vertexArr.count();i++) { QVector3D vertex=vertexArr.at(i)*rotateMat*perspective_mat; vertex_list.push_back(vertex); //找出z值max的頂點(diǎn) 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); } } } //把原點(diǎn)移到中間來 painter.save(); painter.translate(width()/2,height()/2); //繪制front和back六個(gè)面,先計(jì)算路徑再繪制 QList<QPainterPath> element_path_list; //每個(gè)面路徑 QList<float> element_z_values; //每個(gè)面中心點(diǎn)的z值 QList<QPointF> element_z_points; //每個(gè)面中心點(diǎn)在平面對(duì)應(yīng)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)); //單個(gè)面的路徑 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點(diǎn)的就是正交表面可見的 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); } //遠(yuǎn)小近大,還要把包含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); } //根據(jù)計(jì)算好的路徑繪制 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(); //旋轉(zhuǎn)矩陣 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來手動(dòng)計(jì)算遠(yuǎn)小近大,也可以矩陣運(yùn)算 //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 }; }
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
C語言用函數(shù)實(shí)現(xiàn)反彈球消磚塊
這篇文章主要為大家詳細(xì)介紹了C語言用函數(shù)實(shí)現(xiàn)反彈球消磚塊,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-05-05c++中處理相關(guān)數(shù)學(xué)函數(shù)
數(shù)學(xué)庫函數(shù)聲明在 math.h 中,主要有:2013-04-04Visual Studio Code (vscode) 配置C、C++環(huán)境/編寫運(yùn)行C、C++的教程詳解(Windows
這篇文章主要介紹了Visual Studio Code (vscode) 配置C、C++環(huán)境/編寫運(yùn)行C、C++的教程詳解(Windows)【真正的小白版】,圖文詳解介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-03-03c++中虛函數(shù)和純虛函數(shù)的作用與區(qū)別
這篇文章主要介紹了c++中虛函數(shù)和純虛函數(shù)的作用與區(qū)別,需要的朋友可以參考下2014-07-07