Qt使用QPainter绘制一个简单的3D风车

最近无聊,用QPainter画了个简单的风车,效果如下:

Qt使用QPainter绘制一个简单的3D风车_第1张图片

可以看到很多地方都穿摸了,因为绘制时每个填充路径的顺序没法很好的确定,特别是如果出现两个面交叉更没法处理,我能想到的就是拆分成多个小的三角来计算,不过这样CPU的负担就太大了。

还有一个没解决的是万向锁的问题,我还没搞明白,后面学懂了再回来改下。

整体思路就是先定义对象树结构体,一个绘制对象可以有多个面和子节点。绘制的时候先根据当前角度和位置计算出所有节点的位置和角度,然后通过矩阵运算得到最终的坐标值。最后,根据所有面的z值进行排序,从最远的开始填充。手稿:

Qt使用QPainter绘制一个简单的3D风车_第2张图片

代码的github地址(MySimple3D类):https://github.com/gongjianbo/EasyQPainter

主要代码:

#ifndef MYSIMPLE3D_H
#define MYSIMPLE3D_H

#include 
#include 
#include 
#include 
#include 
#include 
//
#include 
#include 
#include 
#include 

//图元结构体
struct My3DMeta
{
    //顶点,可以是任意个
    QList vertex;
    //颜色
    QColor color;
    //QBrush brush;
    //图元顶点中z值最小者,单独作为成员便于排序
    double z;
    //根据定点计算出的路径,便于绘制
    QPainterPath path;

    My3DMeta(const QList &vtx,const QColor &clr)
        :vertex(vtx),color(clr)
    {}
};

//物体实体结构体
struct My3DItem
{
    //相对于场景或者父节点的坐标位置
    QVector3D position;
    //相对于场景或者父节点的方向
    QVector3D rotation;
    //包含的图元
    QList> surfaceMetas;
    //子节点物体列表
    QList> subItems;
    //旋转动画因子(根据全局的定时器步进值计算对应分量动画效果)
    QVector3D animationFactor;

    My3DItem(const QVector3D &pos=QVector3D(0,0,0),
             const QVector3D &rotate=QVector3D(0,0,0),
             const QList> &metas=QList>(),
             const QList> &subs=QList>(),
             const QVector3D &factor=QVector3D(0,0,0))
        :position(pos),rotation(rotate),surfaceMetas(metas),subItems(subs),animationFactor(factor)
    {}

    //根据当前位置和角度计算出顶点列表
    //position取出后直接叠加到顶点得坐标上:vertex+position+this->position
    //rotation目前只计算了x和y的旋转,作用于item的顶点上,毕竟写一个camera太复杂了,所以只能旋转item
    //step为定时器动画的步进,每个item根据自身的动画因子成员来计算
    QList> calculateSurfaceMetas(const QVector3D &position,const QVector3D &rotation,double step)
    {
        QVector3D cur_position=position+this->position;
        QVector3D cur_rotation=rotation+this->rotation;
        for(QSharedPointer meta:surfaceMetas)
        {
            QPainterPath path;
            double z;
            //【注意】我这里还没有去解决万向锁的问题,只有叶子节点才能进行z轴旋转
            //等把Qt这个QQuaternion类搞明白了再回来改
            QMatrix4x4 mat_anim;
            mat_anim.rotate(QQuaternion::fromEulerAngles(step*this->animationFactor));
            //先在正交投影里计算item旋转后的坐标,然后z+=1000,使z值不至于小于0(因为需要透视投影)
            //然后使用透视投影算出近大远小,放大50倍是因为整体太远了看不见
            QMatrix4x4 mat_rotate;
            mat_rotate.rotate(QQuaternion::fromEulerAngles(cur_rotation));
            QMatrix4x4 mat_per;
            mat_per.perspective(45.0f,1.0f,0.1f,2000.0f);
            mat_per.scale(50); //暂时固定50倍放大
            bool is_first=true;
            for(const QVector3D &vertex:meta->vertex)
            {
                QVector3D calc_rotate=(vertex*mat_anim+cur_position)*mat_rotate;
                //暂时固定z+=1000
                QVector3D calc_vertex=(calc_rotate+QVector3D(0,0,1000))*mat_per;
                //qDebug()<path=path;
            meta->z=z;
        }
        QList> surface_metas=surfaceMetas;
        for(QSharedPointer item:subItems)
        {
            surface_metas+=item->calculateSurfaceMetas(cur_position,cur_rotation+step*this->animationFactor,step);
        }

        return surface_metas;
    }
};

//绘制一个3d风车
//目前还存在两个问题:
//1.堆叠顺序计算效果不大好,或许可以将表面裁减为多个小三角来解决;
//2.Qt的四元数类我还不会用,所以物体的相对旋转如果要计算z轴分量就有问题,目前只能叶子节点z轴旋转
class MySimple3D : public QWidget
{
    Q_OBJECT
public:
    explicit MySimple3D(QWidget *parent = nullptr);
    ~MySimple3D();

protected:
    void paintEvent(QPaintEvent *event) override;
    //鼠标操作
    void mousePressEvent(QMouseEvent *event) override;
    void mouseMoveEvent(QMouseEvent *event) override;
    void mouseReleaseEvent(QMouseEvent *event) override;
    //改变窗口大小
    void resizeEvent(QResizeEvent *event) override;

private:
    //初始化操作
    void initItems();
    //绘制
    void drawImage(int width,int height);

private:
    //根实体(这个变量绘制时在线程访问)
    My3DItem rootItem;
    //FPS统计,paintEvent累计temp,达到一秒后赋值给counter
    int fpsCounter=0;
    int fpsTemp=0;
    //FPS计时
    QTime fpsTime;
    //鼠标位置
    QPoint mousePos;
    //鼠标按下标志位
    bool mousePressed=false;
    //旋转角度
    double xRotate=0;
    double yRotate=0;
    //多线程异步watcher
    QFutureWatcher watcher;
    //绘制好的image
    QImage image;
    //定时器旋转步进值
    double animationStep=0;
};

#endif // MYSIMPLE3D_H
#include "MySimple3D.h"

#include 
#include 
#include 
#include 
#include 
#include 

#include 
#include 
#include 

MySimple3D::MySimple3D(QWidget *parent)
    : QWidget(parent)
{
    initItems();

    //异步处理结束,获取结果并刷新窗口
    connect(&watcher,&QFutureWatcher::finished,[this](){
        image=watcher.result();
        update();
    });

    fpsTime=QTime::currentTime();
    fpsTime.start();
    //定时旋转风车
    QTimer *timer=new QTimer(this);
    connect(timer,&QTimer::timeout,[=]{
        animationStep+=2.0;
        drawImage(width(),height());
    });
    timer->start(50);
}

MySimple3D::~MySimple3D()
{
    if(!watcher.isFinished())
        watcher.waitForFinished();
}

void MySimple3D::paintEvent(QPaintEvent *event)
{
    event->accept();
    QPainter painter(this);
    painter.fillRect(this->rect(),Qt::black);

    if(image.size().isValid())
        painter.drawImage(0,0,image);

    //fps统计
    if(fpsTime.elapsed()>1000){
        fpsTime.restart();
        fpsCounter=fpsTemp;
        fpsTemp=0;
    }else{
        fpsTemp++;
    }
    painter.setPen(QPen(Qt::white));
    painter.drawText(10,30,"FPS:"+QString::number(fpsCounter));
    painter.drawText(10,50,"Drag Moving ... ...");
}

void MySimple3D::mousePressEvent(QMouseEvent *event)
{
    mousePressed=true;
    mousePos=event->pos();
    QWidget::mousePressEvent(event);
}

void MySimple3D::mouseMoveEvent(QMouseEvent *event)
{
    if(mousePressed){
        const QPoint posOffset=event->pos()-mousePos;
        mousePos=event->pos();
        //旋转矩阵  x和y分量
        xRotate+=-posOffset.y();
        yRotate+=-posOffset.x();
        //update();
        drawImage(width(),height());
    }
    QWidget::mouseMoveEvent(event);
}

void MySimple3D::mouseReleaseEvent(QMouseEvent *event)
{
    mousePressed=false;
    QWidget::mouseReleaseEvent(event);
}

void MySimple3D::resizeEvent(QResizeEvent *event)
{
    if(event->size().isValid()){
        const int width=event->size().width();
        const int height=event->size().height();
        drawImage(width,height);
    }
    QWidget::resizeEvent(event);
}

void MySimple3D::initItems()
{
    //模板的嵌套时自动格式化太难看了
    //四个扇叶
    My3DMeta* sub_fan1=new My3DMeta{{
            QVector3D(0,0,0),QVector3D(-250,250,0),QVector3D(-300,200,10),QVector3D(-100,0,10)},
            QColor(110,250,250,200)};
    My3DMeta* sub_fan2=new My3DMeta{{
            QVector3D(0,0,0),QVector3D(-250,-250,0),QVector3D(-200,-300,10),QVector3D(0,-100,10)},
            QColor(130,250,250,200)};
    My3DMeta* sub_fan3=new My3DMeta{{
            QVector3D(0,0,0),QVector3D(250,-250,0),QVector3D(300,-200,10),QVector3D(100,0,10)},
            QColor(110,250,250,200)};
    My3DMeta* sub_fan4=new My3DMeta{{
            QVector3D(0,0,0),QVector3D(250,250,0),QVector3D(200,300,10),QVector3D(0,100,10)},
            QColor(130,250,250,200)};
    auto sub_fanmetas=QList>{QSharedPointer(sub_fan1),
                                                     QSharedPointer(sub_fan2),
                                                     QSharedPointer(sub_fan3),
                                                     QSharedPointer(sub_fan4)};
    auto sub_fansubs=QList>{};
    My3DItem *sub_fanitem=new My3DItem{
            QVector3D(0,400,-150),
            QVector3D(0,0,0),
            sub_fanmetas,
            sub_fansubs,
            QVector3D(0,0,-1)}; //给z加了动画因子

    //风车主干,共9个面,顶部尖塔4+主干4+底面
    My3DMeta* sub_main1=new My3DMeta{{
            QVector3D(100,400,100),QVector3D(-100,400,100),QVector3D(0,500,0)},
            QColor(250,0,0)};
    My3DMeta* sub_main2=new My3DMeta{{
            QVector3D(-100,400,100),QVector3D(-100,400,-100),QVector3D(0,500,0)},
            QColor(0,250,0)};
    My3DMeta* sub_main3=new My3DMeta{{
            QVector3D(-100,400,-100),QVector3D(100,400,-100),QVector3D(0,500,0)},
            QColor(0,0,250)};
    My3DMeta* sub_main4=new My3DMeta{{
            QVector3D(100,400,-100),QVector3D(100,400,100),QVector3D(0,500,0)},
            QColor(250,250,0)};
    My3DMeta* sub_main5=new My3DMeta{{
            QVector3D(100,400,100),QVector3D(-100,400,100),QVector3D(-120,0,120),QVector3D(120,0,120)},
            QColor(205,150,100)};
    My3DMeta* sub_main6=new My3DMeta{{
            QVector3D(-100,400,100),QVector3D(-100,400,-100),QVector3D(-120,0,-120),QVector3D(-120,0,120)},
            QColor(220,150,100)};
    My3DMeta* sub_main7=new My3DMeta{{
            QVector3D(-100,400,-100),QVector3D(100,400,-100),QVector3D(120,0,-120),QVector3D(-120,0,-120)},
            QColor(235,150,100)};
    My3DMeta* sub_main8=new My3DMeta{{
            QVector3D(100,400,-100),QVector3D(100,400,100),QVector3D(120,0,120),QVector3D(120,0,-120)},
            QColor(250,150,100)};
    My3DMeta* sub_main9=new My3DMeta{{
            QVector3D(-120,0,120),QVector3D(-120,0,-120),QVector3D(120,0,-120),QVector3D(120,0,120)},
            QColor(200,150,0)};


    auto sub_mainmetas=QList>{QSharedPointer(sub_main1),
                                                      QSharedPointer(sub_main2),
                                                      QSharedPointer(sub_main3),
                                                      QSharedPointer(sub_main4),
                                                      QSharedPointer(sub_main5),
                                                      QSharedPointer(sub_main6),
                                                      QSharedPointer(sub_main7),
                                                      QSharedPointer(sub_main8),
                                                      QSharedPointer(sub_main9)};
    auto sub_mainsubs=QList>{QSharedPointer(sub_fanitem)};
    My3DItem *sub_mainitem=new My3DItem{
            QVector3D(0,0,0),
            QVector3D(0,0,0),
            sub_mainmetas,
            sub_mainsubs};

    //根节点,一个平面,(平面用半透明是为了穿模时看起来没那么别扭)
    My3DMeta* root_meta=new My3DMeta{{
            QVector3D(-200,0,200),QVector3D(200,0,200),
            QVector3D(200,0,-200),QVector3D(-200,0,-200)},
            QColor(255,255,255,100)};
    auto root_metas=QList>{QSharedPointer(root_meta)};
    auto root_subs=QList>{QSharedPointer(sub_mainitem)};
    rootItem=My3DItem{
            QVector3D(0,-300,0),
            QVector3D(0,0,0),
            root_metas,
            root_subs,
            QVector3D(0,0.1f,0)}; //给y加了动画因子
}

void MySimple3D::drawImage(int width, int height)
{
    if(width>10&&height>10&&watcher.isFinished()){
        QVector3D rotate=QVector3D(xRotate,yRotate,0);
        int step=animationStep;

        //多线程绘制到image上,绘制完后返回image并绘制到窗口上
        QFuture futures=QtConcurrent::run([this,width,height,rotate,step](){
            QImage img(width,height,QImage::Format_ARGB32);
            img.fill(Qt::transparent);
            QPainter painter(&img);
            if(!painter.isActive())
                return img;
            painter.fillRect(img.rect(),Qt::black);

            //painter.save();
            //坐标原点移动到中心
            painter.translate(width/2,height/2);

            //计算所有的图元顶点路径
            QList> surface_metas=rootItem.calculateSurfaceMetas(QVector3D(0,0,0),rotate,step);
            //根据z轴排序
            std::sort(surface_metas.begin(),surface_metas.end(),
                      [](const QSharedPointer &left,const QSharedPointer &right){
                return left->zz;
            });
            //根据z值从远处开始绘制图元路径
            for(QSharedPointer meta:surface_metas)
            {
                painter.fillPath(meta->path,meta->color);
            }

            //painter.restore();
            return img;
        });
        watcher.setFuture(futures);
    }
}

 

你可能感兴趣的:(Qt,略知一二,Qt,绘图)