qt+opengl 三维坐标系(三)

文章目录

  • 前言
  • 一、深度测和投影矩阵、观察矩阵
  • 二、绘制坐标系
  • 三、添加箭头
  • 四、添加文字
  • 五、放大缩小
  • 六、旋转
  • 七、移动
  • 八、完整代码
  • 总结


前言

效果如图
在这里插入图片描述


一、深度测和投影矩阵、观察矩阵

这部分不明白,网上查的都是这个步骤,用起来也没问题。

void MOpenGLWidget3D::initializeGL()
{
    initializeOpenGLFunctions();
    glClearColor(0.0, 0.2, 0.3, 1.0);
    glShadeModel(GL_SMOOTH);
    glClearDepth( 1.0 );
    glEnable( GL_DEPTH_TEST );
    glDepthFunc( GL_LEQUAL );
    //所作深度测试的类型。

    //上面这三行必须做的是关于depth buffer(深度缓存)的。将深度缓存设想为屏幕后面的层。深度缓存不断的对物体进入屏幕内部有多深进行跟踪。
    //我们本节的程序其实没有真正使用深度缓存,但几乎所有在屏幕上显示3D场景OpenGL程序都使用深度缓存。它的排序决定那个物体先画。这样您就不会将
    //一个圆形后面的正方形画到圆形上来。深度缓存是OpenGL十分重要的部分。

    glHint( GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST );
    //真正精细的透视修正。这一行告诉OpenGL我们希望进行最好的透视修正。这会十分轻微的影响性能。但使得透视图看起来好一点。
}
void MOpenGLWidget3D::resizeGL(int w, int h)
{
    //防止height为0
    if (h == 0) h = 1;
    //重置当前的视口
    glViewport(0, 0,(GLint)w, (GLint)h);
    //选择投影矩阵
    glMatrixMode(GL_PROJECTION);
    //重置投影矩阵
    glLoadIdentity();
    //建立透视投影矩阵
    gluPerspective(45.0, (GLfloat)w / (GLfloat)h, 0.001, 100.0);
    //选择模型观察矩阵
    glMatrixMode(GL_MODELVIEW);
    //重置模型观察矩阵
    glLoadIdentity();

    //将屏幕设置为透视图, 越远的东西看起来越小, 创建了一个现实外观的场景。
    //此处透视按照基于窗口宽度和高度的m_isize 45度视角来计算。 0.1, 100.0是我们在场景中所能绘制深度的起点和终点
}

二、绘制坐标系

头文件

#include <QObject>
#include <QWidget>
#include <GL/glu.h>
#include <QGL>
#include <QtOpenGL>
#include <QGLWidget>
#include <QOpenGLWidget>
#include <QOpenGLFunctions_4_5_Core>
#include <QGridLayout>
#include <QDebug>

template<class T>
class glVec3{
public :
    glVec3():x(0), y(0), z(0){};
    glVec3(T x, T y, T z) : x(x), y(y), z(z){};
    T x;
    T y;
    T z;

    friend QDebug operator<<(QDebug debug, const glVec3 &gl)
        {
        return debug<<"glVec3(x, y, z)("<<gl.x<<", "<<gl.y<<", "<<gl.z<<")";
    };

protected:
};



class MOpenGLWidget3D : public QOpenGLWidget, QOpenGLFunctions_4_5_Core
{
    Q_OBJECT
public:
    explicit MOpenGLWidget3D(QWidget* parent = nullptr);
public:
    template<class T>
    void GLGrid(glVec3<T> start,  glVec3<T> end, int num);
    template<class T>
    void GLText(glVec3<T> start, QColor color, QString text);


protected:
    // 深度
    int m_scloe;
    // 三维坐标系范围
    int m_isize;
    // 三维坐标系位移参数
    glVec3<float> m_translate;
    // 鼠标按下坐标参数
    glVec3<float> m_press;
    // 窗口位移参数
    glVec3<float> m_rot1;
    glVec3<float> m_rot2;
    //
    QVector<glVec3<float>> m_vecVal;

public slots:
    void UpdateSlots();

protected:
    virtual void initializeGL();
    virtual void resizeGL(int w, int h);
    virtual void paintGL();

protected:
    virtual void mousePressEvent(QMouseEvent *event);
    virtual void mouseReleaseEvent(QMouseEvent *event);
    virtual void mouseMoveEvent(QMouseEvent *event);
    virtual void wheelEvent(QWheelEvent *event);
};

初始化变量

    //坐标系Z轴方向偏移量
    m_scloe = -15;
    //轴长度
    m_isize = 8;
    //XYZ轴交汇原点偏移量,使偏移量是轴长的一半(正负代表方向),是为了使实际原点在坐标系的中心(空间)
    m_translate = glVec3<float>(-m_isize/2.0f,-m_isize/2.0f,-m_isize/2.0f);

先看结果:

注释掉//glTranslatef(m_translate.x, m_translate.y, m_translate.z);

保留glTranslatef(m_translate.x, m_translate.y, m_translate.z);
在这里插入图片描述

结合上图和下面代码说明下glTranslatef(x,y,x);,分两种情况讨论:

glTranslatef(0, 0, m_scloe); 
这里是直接将原矩阵沿Z轴移动s_scloe(向量),而在这个坐标系矩阵中,所有系统都是相对的,其实就等于没有变化(相对于整个坐标系)。
而这个整体的移动的变化是体现在整个窗体中的,体现在坐标系在窗体中显示的远近上。
glPushMatrix();
{
    glTranslatef(m_translate.x, m_translate.y, m_translate.z);
}
glPopMatrix();
与下文示例代码一致,不同点在于glPushMatrix()函数会拷贝原坐标系矩阵压入栈中,此时操作矩阵的是拷贝的矩阵,直到退出栈。
对拷贝的矩阵的操作与原矩阵是互不影响的。
就如上图所示,绘制的白色原点(0,0,0);若需要在XYZ轴交汇处绘制点,就需要
glVertex3f(m_translate.x, m_translate.y, m_translate.z);
或者
> glPushMatrix();
{
    glTranslatef(m_translate.x, m_translate.y, m_translate.z);
    glVertex3f(0,0,0);
}
或者
在描绘坐标系前就glTranslatef(m_translate.x, m_translate.y, m_translate.z);
就直接偏移了原坐标系的位置,也就不需要后面的操作都需要偏移位置
之所以使用,是为了体现这种拷贝原坐标系操作,既不影响原坐标系又能实现图像位置偏移。在示例代码中无法体现出作用,
但是,当窗口中有多个坐标系(例如绘制机器人部件),需要修改其中一个坐标系但又不影响其他坐标系,这种构造方式就很合适了。
void MOpenGLWidget3D::paintGL()
{
    //清除屏幕和深度缓存
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    //重置当前的模型观察矩阵
    glLoadIdentity();

    //!**********************************************************
    //!********************    旋转显示窗口    ********************
    //!**********************************************************
    //将窗口矩阵移动一个向量(0, 0, m_scloe), m_scloe = -15;可以很好的理解为将视图向后(也就是z轴负方向)移动,使整个坐标系展现出来
    // 直接修改了原坐标系矩阵
    // 坐标系放大缩小就是修改该变量
    glTranslatef(0, 0, m_scloe); 
    //绕原点X轴旋转, 通过修改m_rot1坐标系上下偏转角度
    glRotatef(abs(m_rot1.z), m_rot1.x, 0, 0);
    //绕原点Y轴旋转, 通过修改m_rot2坐标系左右偏转角度
    glRotatef(abs(m_rot2.z), 0, m_rot2.y, 0);

    //!**********************************************************
    //!********************   绘制坐标系原点   ********************
    //!**********************************************************
    //将当前矩阵压入栈(压入栈后位移矩阵不影响原矩阵)
    glPushMatrix();
    {
        glColor3f(0.9, 0.9, 0.9);
        glPointSize(8);
        //glTranslatef(x, y, z)分别指定沿x,y,z轴方向的平移分量;在当前原点的基础上平移一个(x,y,z)向量
        //原点(0,0,0) =>  (m_translate.x, m_translate.y, m_translate.z)
        //将矩阵位移,使坐标系正好在窗口中心(实际的坐标系原点不变,屏幕中显示的坐标系原点是偏移了如下设置的偏移量)
        // glPushMatrix()拷贝了原矩阵并要入栈中,退出栈前,修改的是拷贝的矩阵,不影响原矩阵
        glTranslatef(m_translate.x, m_translate.y, m_translate.z); 
        glBegin(GL_POINTS);
        {
            glVertex3f(0, 0, 0);
        }
        glEnd();
    }
    glPopMatrix();

    //!**********************************************************
    //!********************     绘制坐标系     ********************
    //!**********************************************************
    //! X轴
    {
        // X轴栅格
        glPushMatrix();
        {
            glColor3f(1, 0, 0);
            glTranslatef(m_translate.x, m_translate.y, m_translate.z);
            glRotatef(90, 1, 0, 0.0);
            GLGrid(glVec3(0.0f, 0.0f, 0.0f), glVec3(8.0f, 8.0f, 0.0f), 40);
        }
        glPopMatrix();
    }
    ```
```cpp
    //! Y轴
    {
        glPushMatrix();//将当前矩阵压入栈
        {
            glColor3f(1, 1, 0);
            glTranslatef(m_translate.x, m_translate.y, m_translate.z);
            glRotatef(0, -1, 0, 0);
            GLGrid(glVec3(0.0f, 0.0f, 0.0f), glVec3(8.0f, 8.0f, 0.0f), 40);
        }
        glPopMatrix();
    }
    //! Z轴
    {
        glPushMatrix();//将当前矩阵压入栈
        {
            glColor3f(0, 1, 1);
            glTranslatef(m_translate.x, m_translate.y, m_translate.z);
            //glRotatef(90, 0, 0, 1);
            GLGrid(glVec3(0.0f, 0.0f, 0.0f), glVec3(0.0f, 8.0f, 8.0f), 40);
        }
        glPopMatrix();
    }
 }

绘制轴线的函数

//!
//! \brief MOpenGLWidget3D::GLGrid
//! \param start 起始点坐标
//! \param end   终点坐标(网格类型即起始点对角坐标)
//! \param num   数量
//!
//! 起点终点的Z值最好为0,
//!
template<class T>
void MOpenGLWidget3D::GLGrid(glVec3<T> start,  glVec3<T> end, int num)
{
    const T xSpacing = (end.x - start.x) / num;
    const T ySpacing = (end.y - start.y) / num;
    const T zSpacing = (end.z - start.z) / num;

    glLineWidth(0.1f);
    glLineStipple(1, 0x0303);//线条样式

    glBegin(GL_LINES);
    {
        glEnable(GL_LINE_SMOOTH);
        for(int i = 0; i < num; ++i)
        {
            T x = start.x + xSpacing * i;
            T y = start.y + ySpacing * i;
            T z = start.z + zSpacing * i;

            // 横线
            glVertex3f(start.x, y, z);
            glVertex3f(end.x, y, z);
            // 竖线
            glVertex3f(x, start.y, z);
            glVertex3f(x, end.y, z);
            // 垂直于 横线 与 竖线 所在平面
            glVertex3f(x, y, start.z);
            glVertex3f(x, y, end.z);
        }
    }
    glEnd();
}

三、添加箭头

//GLUquadricObj*, 底部半径, 顶部半径, 高, 圆柱体围绕z轴细分为切片, 沿 z 轴细分为堆栈
gluCylinder(objCylinder, 0.1, 0.0, 0.2, 100, 1);
绘制的 底部半径为0.1,顶部半径为0, 高为0.2的圆柱(圆锥)

在这里插入图片描述

//创建并返回指向新 quadric 对象的指针。 调用四边形呈现和控制函数时,请参阅此对象。
//返回值为零表示没有足够的内存分配给对象。
GLUquadricObj *objCylinder = gluNewQuadric();
//X轴箭头
glPushMatrix();
{
    glColor3f(1, 0, 0);
    // 1. glTranslatef(m_translate.x, m_translate.y, m_translate.z);
    // 2. glTranslatef(m_isize, 0, 0);
    // 1: 偏移到指定点 2:偏移轴长; 合成一步
    glTranslatef(m_translate.x + m_isize, m_translate.y, m_translate.z);
    glRotatef(90, 0, 1, 0.0);
    //GLUquadricObj*, 底部半径, 顶部半径, 高, 圆柱体围绕z轴细分为切片, 沿 z 轴细分为堆栈
    gluCylinder(objCylinder, 0.1, 0.0, 0.2, 100, 1);
}
glPopMatrix();
//Y轴箭头
glPushMatrix();
{
    glColor3f(1, 1, 0);
    glTranslatef(m_translate.x, m_translate.y+m_isize, m_translate.z);
    glRotatef(90, -1, 0, 0);
    gluCylinder(objCylinder, 0.1, 0.0, 0.2, 100, 1);
}
glPopMatrix();
//Z轴箭头
glPushMatrix();
{
    glColor3f(0, 1, 1);
    glTranslatef(m_translate.x, m_translate.y, m_translate.z+m_isize);
    //glRotatef(90, 0, 0, 1);
    gluCylinder(objCylinder, 0.06, 0.0, 0.12, 100, 1);
}
glPopMatrix();

四、添加文字

绘制文字步骤:

  1. QPainterPath 得到文字路径
  2. toSubpathPolygons()将文字路径转换成多边形,以列表返回
  3. 遍历返回列表获取得到每个文字的多边形列表
  4. 遍历每个多边形列表并将这组列表绘制线条,用GL_LINE_STRIP。

在这里插入图片描述

//X轴文字
glPushMatrix();
{
    GLText(glVec3<float>(8.0f, 0.0f, 0.0f), QColor(255, 0, 0), "X");
}
glPopMatrix();
//Y轴文字
glPushMatrix();
{
    GLText(glVec3<float>(0.0f, 8.0f, 0.0f), QColor(255, 255, 0), "Y");
}
glPopMatrix();
//Z轴文字
glPushMatrix();
{
    GLText(glVec3<float>(0.0f, 0.0f, 8.0f), QColor(0, 255, 255), "Z");
}
glPopMatrix();

绘制文字的函数

template<class T>
void MOpenGLWidget3D::GLText(glVec3<T> start, QColor color, QString text)
{
    glTranslatef(m_translate.x, m_translate.y, m_translate.z);

    //生成文字绘制路径
    QPainterPath path;
    path.addText(QPointF((start.x+start.y+start.z)/2, 0), QFont("Microsoft YaHei UI", 2), text);
    //转换成多边形列表
    QList<QPolygonF> poly = path.toSubpathPolygons();
    //
    QList<QPolygonF>::iterator iter = poly.begin();
    while(iter != poly.end())
    {
        glLineWidth(1);
        glBegin(GL_LINE_LOOP);
        {
            QPolygonF::iterator it = (*iter).begin();
            while(it != iter->end())
            {
                glVertex3f(start.x + it->rx() * 0.1, start.y - it->ry() * 0.1, start.z+0.02);
                glColor3f(color.redF(), color.greenF(), color.blueF());
                it++;
            }
        }
        glEnd();
        iter++;
    }
}

五、放大缩小

重写鼠标滚轮事件,通过滚动的角度合理的改变m_scloe的值(scloe的值决定原坐标系矩阵位置)

void MOpenGLWidget3D::wheelEvent(QWheelEvent *event)
{
    QOpenGLWidget::wheelEvent(event);

    if (event->angleDelta().y() > 0) {
        m_scloe += m_scloe < -1 ? 1 : 0;

    } else if (event->angleDelta().y() < 0) {
        m_scloe -= m_scloe > -100 ? 1 : 0;
    }
    update();
}

六、旋转

glRotatef(angle, x, y , z);
x、y、z 都为0时,绕x轴旋转angle
x不为0时绕x轴转angle,y、z同理
x、y不为0时,绕x轴旋转angle且绕y轴旋转angle,其他同理

旋转可以分解为左右旋转(绕Y轴)和上下旋转(绕X轴)
所以有:

//绕原点X轴旋转, 通过修改m_rot1坐标系上下偏转角度
glRotatef(abs(m_rot1.z), m_rot1.x, 0, 0);
//绕原点Y轴旋转, 通过修改m_rot2坐标系左右偏转角度
glRotatef(abs(m_rot2.z), 0, m_rot2.y, 0);

从下文示例代码中可以看出 m_rot1.z == m_rot1.x; m_rot2.z = m_rot1.y; 之所以让这两个值相等,是应为:
m_rot1.z 或 m_rot2.z代表的是旋转角度,且这个角度是有方向的(正负),而控制旋转的x、y、z只要不为0就可以控制旋转方向,旋转的角度直接就可以取m_rot.z的绝对值。
···
(如果控制旋转方向的x、y不与角度z用同一个值,就会出现z不为0,但下,y为0的情况,这种情况下就成了glRotatef(abs(m_rot2.z), 0, 0, 0); 默认绕X轴旋转,实际应为不旋转的)
注释:m_rot1.z和 m_rot2.z存储的旋转角度

void MOpenGLWidget3D::mousePressEvent(QMouseEvent *event)
{
    QOpenGLWidget::mousePressEvent(event);

    QPoint pos = event->pos();
    GLdouble modelview[16];
    GLdouble projection[16];
    GLint viewport[4];
    GLdouble wX, wY, wZ;

    glGetDoublev(GL_MODELVIEW_MATRIX, modelview);
    glGetDoublev(GL_PROJECTION_MATRIX, projection);
    glGetIntegerv(GL_VIEWPORT, viewport);

    // 将鼠标坐标转换为世界坐标
    gluUnProject(pos.x(), pos.y(), 0, modelview, projection, viewport, &wX, &wY, &wZ);
    m_press.x = wX;
    m_press.y = wY;
    m_press.z = 0;
}
void  MOpenGLWidget3D::mouseReleaseEvent(QMouseEvent *event)
{
    QOpenGLWidget::mouseReleaseEvent(event);
    m_press.x = 0;
    m_press.y = 0;
    m_press.z = 0;
}
void MOpenGLWidget3D::mouseMoveEvent(QMouseEvent *event)
{
    QOpenGLWidget::mouseMoveEvent(event);

    QPoint pos = event->pos();

    GLdouble modelview[16];
    GLdouble projection[16];
    GLint viewport[4];
    GLdouble wx, wy, wz;
    glGetDoublev(GL_MODELVIEW_MATRIX, modelview);
    glGetDoublev(GL_PROJECTION_MATRIX, projection);
    glGetIntegerv(GL_VIEWPORT, viewport);
    // 将鼠标坐标转换为世界坐标
    gluUnProject(pos.x(), pos.y(), 0, modelview, projection, viewport, &wx, &wy, &wz);

    if(event->buttons() & Qt::LeftButton)
    {
        //上下旋转, 绕X轴,计算的鼠标移动值(不是旋转角度)
        //只要m_rot.x不为0,就绕x旋转
        // glRotatef(abs(m_rot1.z), m_rot1.x, 0, 0);
        // m_rot1.z = m_rot1.x的作用是保持旋转的角度与实际旋转方向一致,能这么做的原因是:只要m_rot.x不为0,就绕x旋转,正负值控制方向(因此角度值z取绝对值)
        m_rot1.x += (wy-m_press.y)*100;
        m_rot1.z = m_rot1.x;
        //左右旋转, 绕Y轴(将 || 改为 && 即可让鼠标左键控制上下翻转,右键控制左右翻转)
        //同上
        m_rot2.y += (wx-m_press.x)*100;
        m_rot2.z = m_rot2.y;
    }

    //if(event->buttons() & Qt::RightButton)
    //{
    //    m_translate.x += (wx-m_press.x)*10.0;
    //    m_translate.y += -(wy-m_press.y)*10.0;
    //}

    m_press.x = wx;
    m_press.y = wy;
    m_press.z = 0;

    update();
}

七、移动

修改控制偏移的变量

void MOpenGLWidget3D::mouseMoveEvent(QMouseEvent *event)
{
    QOpenGLWidget::mouseMoveEvent(event);

    QPoint pos = event->pos();

    GLdouble modelview[16];
    GLdouble projection[16];
    GLint viewport[4];
    GLdouble wx, wy, wz;
    glGetDoublev(GL_MODELVIEW_MATRIX, modelview);
    glGetDoublev(GL_PROJECTION_MATRIX, projection);
    glGetIntegerv(GL_VIEWPORT, viewport);
    // 将鼠标坐标转换为世界坐标
    gluUnProject(pos.x(), pos.y(), 0, modelview, projection, viewport, &wx, &wy, &wz);

    if(event->buttons() & Qt::RightButton)
    {
        m_translate.x += (wx-m_press.x)*10.0;
        m_translate.y += -(wy-m_press.y)*10.0;
    }
    
    m_press.x = wx;
    m_press.y = wy;
    m_press.z = 0;

    update();
}

八、完整代码

在这里插入图片描述

#ifndef MOPENGLWIDGET3D_H
#define MOPENGLWIDGET3D_H

#include <QObject>
#include <QWidget>
#include <GL/glu.h>
#include <QGL>
#include <QtOpenGL>
#include <QGLWidget>
#include <QOpenGLWidget>
#include <QOpenGLFunctions_4_5_Core>
#include <QGridLayout>
#include <QDebug>

template<class T>
class glVec3{
public :
    glVec3():x(0), y(0), z(0){};
    glVec3(T x, T y, T z) : x(x), y(y), z(z){};
    T x;
    T y;
    T z;

    friend QDebug operator<<(QDebug debug, const glVec3 &gl)
        {
        return debug<<"glVec3(x, y, z)("<<gl.x<<", "<<gl.y<<", "<<gl.z<<")";
    };

protected:
};



class MOpenGLWidget3D : public QOpenGLWidget, QOpenGLFunctions_4_5_Core
{
    Q_OBJECT
public:
    explicit MOpenGLWidget3D(QWidget* parent = nullptr);
public:
    template<class T>
    void GLGrid(glVec3<T> start,  glVec3<T> end, int num);
    template<class T>
    void GLText(glVec3<T> start, QColor color, QString text);


protected:
    // 深度
    int m_scloe;
    // 三维坐标系范围
    int m_isize;
    // 三维坐标系位移参数
    glVec3<float> m_translate;
    // 鼠标按下坐标参数
    glVec3<float> m_press;
    // 窗口位移参数
    glVec3<float> m_rot1;
    glVec3<float> m_rot2;
    //
    QVector<glVec3<float>> m_vecVal;

public slots:
    void UpdateSlots();

protected:
    virtual void initializeGL();
    virtual void resizeGL(int w, int h);
    virtual void paintGL();

protected:
    virtual void mousePressEvent(QMouseEvent *event);
    virtual void mouseReleaseEvent(QMouseEvent *event);
    virtual void mouseMoveEvent(QMouseEvent *event);
    virtual void wheelEvent(QWheelEvent *event);
};

#endif // MOPENGLWIDGET3D_H
#include "mopenglwidget3d.h"
#include <QDebug>
#define PI 3.1415926

MOpenGLWidget3D::MOpenGLWidget3D(QWidget *parent)
    : QOpenGLWidget{parent}
{
    //坐标系Z轴方向偏移量
    m_scloe = -15;
    //轴长度
    m_isize = 8;
    //XYZ轴交汇原点偏移量,使偏移量是轴长的一半(正负代表方向),是为了使实际原点在坐标系的中心(空间)
    m_translate = glVec3<float>(-m_isize/2.0f,-m_isize/2.0f,-m_isize/2.0f);

    QTimer* timer = new QTimer(this);
    timer->setInterval(10);
    connect(timer, SIGNAL(timeout()), this, SLOT(UpdateSlots()));
    timer->start();

    //设置多采样的值
    QSurfaceFormat fmt = format();
    fmt.setSamples(18);
    setFormat(fmt);
}

void MOpenGLWidget3D::UpdateSlots()
{
    static float valx = 0;
    glVec3<float> m_value(valx*0.1, sin(valx)*0.1, cos(valx)*0.1);
    valx+= 0.05f;
    m_vecVal.push_back(m_value);

    //if(m_vecVal.size()>1000) m_vecVal.pop_front();

    update();
}
void MOpenGLWidget3D::initializeGL()
{
    initializeOpenGLFunctions();
    glClearColor(0.0, 0.2, 0.3, 1.0);


    glShadeModel(GL_SMOOTH);
    glClearDepth( 1.0 );
    glEnable( GL_DEPTH_TEST );
    glDepthFunc( GL_LEQUAL );
    //所作深度测试的类型。

    //上面这三行必须做的是关于depth buffer(深度缓存)的。将深度缓存设想为屏幕后面的层。深度缓存不断的对物体进入屏幕内部有多深进行跟踪。
    //我们本节的程序其实没有真正使用深度缓存,但几乎所有在屏幕上显示3D场景OpenGL程序都使用深度缓存。它的排序决定那个物体先画。这样您就不会将
    //一个圆形后面的正方形画到圆形上来。深度缓存是OpenGL十分重要的部分。

    glHint( GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST );
    //真正精细的透视修正。这一行告诉OpenGL我们希望进行最好的透视修正。这会十分轻微的影响性能。但使得透视图看起来好一点。
}

void MOpenGLWidget3D::resizeGL(int w, int h)
{
    //防止height为0
    if (h == 0) { h = 1; }
    //重置当前的视口
    glViewport(0, 0,(GLint)w, (GLint)h);
    //选择投影矩阵
    glMatrixMode(GL_PROJECTION);
    //重置投影矩阵
    glLoadIdentity();
    //建立透视投影矩阵
    gluPerspective(45.0, (GLfloat)w / (GLfloat)h, 0.001, 100.0);
    //选择模型观察矩阵
    glMatrixMode(GL_MODELVIEW);
    //重置模型观察矩阵
    glLoadIdentity();

    //将屏幕设置为透视图, 越远的东西看起来越小, 创建了一个现实外观的场景。
    //此处透视按照基于窗口宽度和高度的m_isize 45度视角来计算。 0.1, 100.0是我们在场景中所能绘制深度的起点和终点
}

//glRotatef(angle, x, y, z);
//默认逆时针旋转angle
// - 表示逆时针, + 表示顺时针
//xyz都为0时默认以x轴旋转
//xyz不为0的表示以不为0的轴旋转角度(转动方向由angle和x\ angle和y \ angle和z 的正负共同决定)
void MOpenGLWidget3D::paintGL()
{
    //清除屏幕和深度缓存
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    //重置当前的模型观察矩阵
    glLoadIdentity();

    //!**********************************************************
    //!********************    旋转显示窗口    ********************
    //!**********************************************************
    //将窗口矩阵移动一个向量(0, 0, m_scloe), m_scloe = -15;可以很好的理解为将视图向后(也就是z轴负方向)移动,使整个坐标系展现出来
    // 直接修改了原坐标系矩阵
    // 坐标系放大缩小就是修改该变量
    glTranslatef(0, 0, m_scloe);
    //绕原点X轴旋转, 通过修改m_rot1坐标系上下偏转角度
    glRotatef(abs(m_rot1.z), m_rot1.x, 0, 0);
    //绕原点Y轴旋转, 通过修改m_rot2坐标系左右偏转角度
    glRotatef(abs(m_rot2.z), 0, m_rot2.y, 0);


    //!**********************************************************
    //!********************   绘制坐标系原点   ********************
    //!**********************************************************
    //将当前矩阵压入栈(压入栈后位移矩阵不影响原矩阵)
    glPushMatrix();
    {
        glColor3f(0.9, 0.9, 0.9);
        glPointSize(8);
        //glTranslatef(x, y, z)分别指定沿x,y,z轴方向的平移分量;在当前原点的基础上平移一个(x,y,z)向量
        // 将矩阵位移,使坐标系正好在窗口中心(实际的坐标系原点不变,屏幕中显示的坐标系原点是偏移了如下设置的偏移量)
        // glPushMatrix()拷贝了原矩阵并要入栈中,退出栈前,修改的是拷贝的矩阵,不影响原矩阵
        glTranslatef(m_translate.x, m_translate.y, m_translate.z);
        glBegin(GL_POINTS);
        {
            glVertex3f(0, 0, 0);
        }
        glEnd();
    }
    glPopMatrix();


    //!**********************************************************
    //!********************     绘制坐标系     ********************
    //!**********************************************************

    //创建并返回指向新 quadric 对象的指针。 调用四边形呈现和控制函数时,请参阅此对象。 返回值为零表示没有足够的内存分配给对象。
    GLUquadricObj *objCylinder = gluNewQuadric();

    //! X轴
    {
        // X轴栅格
        glPushMatrix();
        {
            glColor3f(1, 0, 0);
            glTranslatef(m_translate.x, m_translate.y, m_translate.z);
            glRotatef(90, 1, 0, 0.0);
            GLGrid(glVec3(0.0f, 0.0f, 0.0f), glVec3(8.0f, 8.0f, 0.0f), 40);
        }
        glPopMatrix();

        //X轴箭头
        glPushMatrix();
        {
            glColor3f(1, 0, 0);
            // 1. glTranslatef(m_translate.x, m_translate.y, m_translate.z);
            // 2. glTranslatef(m_isize, 0, 0);
            // 1: 偏移到指定点 2:偏移轴长; 合成一步
            glTranslatef(m_translate.x + m_isize, m_translate.y, m_translate.z);
            glRotatef(90, 0, 1, 0.0);
            //GLUquadricObj*, 底部半径, 顶部半径, 高, 圆柱体围绕z轴细分为切片, 沿 z 轴细分为堆栈
            gluCylinder(objCylinder, 0.1, 0.0, 0.2, 100, 1);
        }
        glPopMatrix();

        //X轴文字
        glPushMatrix();
        {
            GLText(glVec3<float>(8.0f, 0.0f, 0.0f), QColor(255, 0, 0), "X");
        }
        glPopMatrix();
    }
    //! Y轴
    {
        glPushMatrix();//将当前矩阵压入栈
        {
            glColor3f(1, 1, 0);
            glTranslatef(m_translate.x, m_translate.y, m_translate.z);
            glRotatef(0, -1, 0, 0);
            GLGrid(glVec3(0.0f, 0.0f, 0.0f), glVec3(8.0f, 8.0f, 0.0f), 40);
        }
        glPopMatrix();

        glPushMatrix();
        {
            glColor3f(1, 1, 0);
            glTranslatef(m_translate.x, m_translate.y+m_isize, m_translate.z);
            glRotatef(90, -1, 0, 0);
            gluCylinder(objCylinder, 0.1, 0.0, 0.2, 100, 1);
        }
        glPopMatrix();

        glPushMatrix();
        {
            GLText(glVec3<float>(0.0f, 8.0f, 0.0f), QColor(255, 255, 0), "Y");
        }
        glPopMatrix();
    }

    //! Z轴
    {
        glPushMatrix();//将当前矩阵压入栈
        {
            glColor3f(0, 1, 1);
            glTranslatef(m_translate.x, m_translate.y, m_translate.z);
            //glRotatef(90, 0, 0, 1);
            GLGrid(glVec3(0.0f, 0.0f, 0.0f), glVec3(0.0f, 8.0f, 8.0f), 40);
        }
        glPopMatrix();

        glPushMatrix();
        {
            glColor3f(0, 1, 1);
            glTranslatef(m_translate.x, m_translate.y, m_translate.z+m_isize);
            //glRotatef(90, 0, 0, 1);
            gluCylinder(objCylinder, 0.06, 0.0, 0.12, 100, 1);
        }
        glPopMatrix();

        glPushMatrix();
        {
            GLText(glVec3<float>(0.0f, 0.0f, 8.0f), QColor(0, 255, 255), "Z");
        }
        glPopMatrix();
    }

    //!**********************************************************
    //!********************    添加动态数据    ********************
    //!**********************************************************

    glPushMatrix();
    {
        glTranslatef(m_translate.x, m_translate.y, m_translate.z);
        glTranslatef(m_isize/2, m_isize/2, m_isize/2);

        glLineWidth(0.2);
        glColor3f(1, 0, 0);
        //开始绘制连续的线
        glBegin(GL_LINE_STRIP);
        {
            for (int i = 0; i < m_vecVal.size(); i++) {
                glVertex3f(m_vecVal[i].x, m_vecVal[i].y, m_vecVal[i].z);
                glFlush(); //强制刷新
            }
        }
        glEnd();
    }
    glPopMatrix();

    glPushMatrix();
    {
        glTranslatef(m_translate.x, m_translate.y, m_translate.z);
        glTranslatef(m_isize/2, m_isize/2, m_isize/2);
        glLineWidth(0.2);
        glColor3f(1, 1, 0);
        glBegin(GL_LINE_STRIP);
        {
            for (int i = 0; i < m_vecVal.size(); i++) {
                glVertex3f(m_vecVal[i].z, m_vecVal[i].x, m_vecVal[i].y);
            }
        }
        glEnd();
        glFlush();
    }
    glPopMatrix();

    glPushMatrix();
    {
        glTranslatef(m_translate.x, m_translate.y, m_translate.z);
        glTranslatef(m_isize/2, m_isize/2, m_isize/2);
        glLineWidth(0.2);
        glColor3f(0, 1, 1);
        glBegin(GL_LINE_STRIP);
        {
            for (int i = 0; i < m_vecVal.size(); i++) {
                glVertex3f(m_vecVal[i].y, m_vecVal[i].z, m_vecVal[i].x);
            }
        }
        glEnd();
        glFlush();
    }
    glPopMatrix();
}

//!
//! \brief MOpenGLWidget3D::GLGrid
//! \param start 起始点坐标
//! \param end   终点坐标(网格类型即起始点对角坐标)
//! \param num   数量
//!
//! 起点终点的Z值最好为0,
//!
template<class T>
void MOpenGLWidget3D::GLGrid(glVec3<T> start,  glVec3<T> end, int num)
{
    const T xSpacing = (end.x - start.x) / num;
    const T ySpacing = (end.y - start.y) / num;
    const T zSpacing = (end.z - start.z) / num;

    glLineWidth(0.1f);
    glLineStipple(1, 0x0303);//线条样式

    glBegin(GL_LINES);
    {
        glEnable(GL_LINE_SMOOTH);
        for(int i = 0; i < num; ++i)
        {
            T x = start.x + xSpacing * i;
            T y = start.y + ySpacing * i;
            T z = start.z + zSpacing * i;

            // 横线
            glVertex3f(start.x, y, z);
            glVertex3f(end.x, y, z);
            // 竖线
            glVertex3f(x, start.y, z);
            glVertex3f(x, end.y, z);
            // 垂直于 横线 与 竖线 所在平面
            glVertex3f(x, y, start.z);
            glVertex3f(x, y, end.z);
        }
    }
    glEnd();
}


template<class T>
void MOpenGLWidget3D::GLText(glVec3<T> start, QColor color, QString text)
{
    glTranslatef(m_translate.x, m_translate.y, m_translate.z);

    //生成文字绘制路径
    QPainterPath path;
    path.addText(QPointF((start.x+start.y+start.z)/2, 0), QFont("Microsoft YaHei UI", 2), text);
    //转换成多边形列表
    QList<QPolygonF> poly = path.toSubpathPolygons();
    //
    QList<QPolygonF>::iterator iter = poly.begin();
    while(iter != poly.end())
    {
        glLineWidth(1);
        glBegin(GL_LINE_LOOP);
        {
            QPolygonF::iterator it = (*iter).begin();
            while(it != iter->end())
            {
                glVertex3f(start.x + it->rx() * 0.1, start.y - it->ry() * 0.1, start.z+0.02);
                glColor3f(color.redF(), color.greenF(), color.blueF());
                it++;
            }
        }
        glEnd();
        iter++;
    }
}



void MOpenGLWidget3D::wheelEvent(QWheelEvent *event)
{
    QOpenGLWidget::wheelEvent(event);

    if (event->angleDelta().y() > 0) {
        m_scloe += m_scloe < -1 ? 1 : 0;

    } else if (event->angleDelta().y() < 0) {
        m_scloe -= m_scloe > -100 ? 1 : 0;
    }

    update();
}

void MOpenGLWidget3D::mousePressEvent(QMouseEvent *event)
{
    QOpenGLWidget::mousePressEvent(event);

    QPoint pos = event->pos();
    GLdouble modelview[16];
    GLdouble projection[16];
    GLint viewport[4];
    GLdouble wX, wY, wZ;

    glGetDoublev(GL_MODELVIEW_MATRIX, modelview);
    glGetDoublev(GL_PROJECTION_MATRIX, projection);
    glGetIntegerv(GL_VIEWPORT, viewport);

    // 将鼠标坐标转换为世界坐标
    gluUnProject(pos.x(), pos.y(), 0, modelview, projection, viewport, &wX, &wY, &wZ);
    m_press.x = wX;
    m_press.y = wY;
    m_press.z = 0;
}

void  MOpenGLWidget3D::mouseReleaseEvent(QMouseEvent *event)
{
    QOpenGLWidget::mouseReleaseEvent(event);
    m_press.x = 0;
    m_press.y = 0;
    m_press.z = 0;
}

void MOpenGLWidget3D::mouseMoveEvent(QMouseEvent *event)
{
    QOpenGLWidget::mouseMoveEvent(event);

    QPoint pos = event->pos();

    GLdouble modelview[16];
    GLdouble projection[16];
    GLint viewport[4];
    GLdouble wx, wy, wz;
    glGetDoublev(GL_MODELVIEW_MATRIX, modelview);
    glGetDoublev(GL_PROJECTION_MATRIX, projection);
    glGetIntegerv(GL_VIEWPORT, viewport);
    // 将鼠标坐标转换为世界坐标
    gluUnProject(pos.x(), pos.y(), 0, modelview, projection, viewport, &wx, &wy, &wz);

    if(event->buttons() & Qt::LeftButton)
    {
        //上下旋转, 绕X轴,计算的鼠标移动值(不是旋转角度)
        //只要m_rot.x不为0,就绕x旋转
        // glRotatef(abs(m_rot1.z), m_rot1.x, 0, 0);
        // m_rot1.z = m_rot1.x的作用是保持旋转的角度与实际旋转方向一致,能这么做的原因是:只要m_rot.x不为0,就绕x旋转,正负值控制方向(因此角度值z取绝对值)
        m_rot1.x += (wy-m_press.y)*100;
        m_rot1.z = m_rot1.x;
        //左右旋转, 绕Y轴(将 || 改为 && 即可让鼠标左键控制上下翻转,右键控制左右翻转)
        //同上
        m_rot2.y += (wx-m_press.x)*100;
        m_rot2.z = m_rot2.y;
    }

    if(event->buttons() & Qt::RightButton)
    {
        m_translate.x += (wx-m_press.x)*10.0;
        m_translate.y += -(wy-m_press.y)*10.0;
    }

    m_press.x = wx;
    m_press.y = wy;
    m_press.z = 0;

    update();
}

总结

我也不知道讲的有没有错,反正运行效果如图,有不对的地方感谢指正啊。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/121151.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

3.29每日一题(微分方程的几何应用题:重点考察)

1、画图&#xff0c;把题目中的条件标出来 2、通过题目中的条件设出正确的微分方程&#xff08;解题的关键&#xff09; 注&#xff1a;用点斜式设方程的时候&#xff0c;注意Y - y y&#xff08;X - x&#xff09;中&#xff08;x&#xff0c;y&#xff09;为曲边上的动点&a…

jenkins Java heap space

jenkins Java heap space&#xff0c;是内存不够。 两个解决方案&#xff1a; 一&#xff0c;修改配置文件 windows系统中&#xff0c;找到Jenkins的安装路径&#xff0c; 修改jenkins.xml 将 -Xmx256m 改为 -Xmx1024m 或者更大 重启jenkins服务。 二&#xff0c;jenkins增…

ubuntu 20.04 server安装

ubuntu 20.04 server安装 ubuntu-20.04.6-live-server-amd64.iso 安装 安装ubuntu20.04 TLS系统后&#xff0c;开机卡在“A start job is running for wait for network to be Configured”等待连接两分多钟。 cd /etc/systemd/system/network-online.target.wants/在[Servi…

基于springboot实现智慧外贸平台系统【项目源码+论文说明】

基于springboot实现智慧外贸平台管理系统演示 摘要 网络的广泛应用给生活带来了十分的便利。所以把智慧外贸管理与现在网络相结合&#xff0c;利用java技术建设智慧外贸平台&#xff0c;实现智慧外贸的信息化。则对于进一步提高智慧外贸管理发展&#xff0c;丰富智慧外贸管理经…

基于OCC+OSG集成框架下的GMSH之二阶网格划分探索

二阶网格相对于一阶网格&#xff0c;其计算节点数量更多&#xff0c;具体表现在一个一阶网格下的三角形中的每个边的中点构建一个点&#xff0c;对一阶三角形网格划分成四个三角形。gmsh提供了网格阶数设置&#xff0c;一般默认是一阶网格&#xff0c;本人根据gmsh文档&#xf…

dgl安装教程

我在矩池云服务器上安装了一个dgl的环境&#xff0c;以后都可以用这个了 首先我的基础环境是 最终的版本如下 安装步骤如下 pip install dgl0.9.1 -f https://s3.us-west-2.amazonaws.com/dgl-data/wheels/cu113/repo.html注意不能直接使用 pip install dgl -f https://s…

001. 变量、环境变量

1、在终端中显示输出 shell脚本通常以shebang起始&#xff1a;#&#xff01;/bin/bash/ shebang是一个文本行&#xff0c;其中#!位于解释器路径之前。/bin/bash是Bash的解释器命令路径。bash将以#符号开头的行视为注释。脚本中只有第一行可以使用shebang来定义解释该脚本所使…

持续交付-Jenkinsfile 语法

实现 Pipeline 功能的脚本语言叫做 Jenkinsfile&#xff0c;由 Groovy 语言实现。Jenkinsfile 一般是放在项目根目录&#xff0c;随项目一起受源代码管理软件控制&#xff0c;无需像创建"自由风格"项目一样&#xff0c;每次可能需要拷贝很多设置到新项目&#xff0c;…

SpringBoot中的Environment

暂且理解成整个application.properties 通过Environment 可以访问application.properties中的任何配置 这里用yml配置 具体实用

docker部署tomcat

1.下载tomcat镜像 尽量去下载最新版本 直接输入docker pull tomcat 后面不跟版本号(要是跟版本号&#xff0c;你还要去官网去查看是否有此版本&#xff0c;太麻烦了) 2.查看镜像 3.通过镜像去run启动容器 -d 就是后台运行 --name 给容器取个新名字 -p 3355:8080…

淘宝/天猫获取商品历史价格信息 API 返回值说明

获取商品历史价格接口的业务场景主要是用于电商平台的开发。这些接口可以提供商品的历史价格信息&#xff0c;帮助开发者更好地了解商品的情况&#xff0c;为消费者提供更准确的价格参考。 在电商平台上&#xff0c;消费者常常需要了解商品的历史价格信息&#xff0c;以判断当…

数字孪生与电力行业的完美融合

电力行业一直是现代社会不可或缺的一部分&#xff0c;而数字孪生技术正逐渐改变这一传统行业的面貌。数字孪生电力解决方案通过将物理世界与数字世界相结合&#xff0c;为电力行业带来了前所未有的机会和挑战。本文为大家介绍山海鲸电力行业系列解决方案&#xff0c;带大家了解…

龙迅LT8911EXB功能概述 MIPICSI/DSI TO EDP

LT8911EXB 描述&#xff1a; Lontium LT8911EXB是MIPIDSI/CSI到eDP转换器&#xff0c;单端口MIPI接收器有1个时钟通道和4个数据通道&#xff0c;每个数据通道最大运行2.0Gbps&#xff0c;最大输入带宽为8.0Gbps。转换器解码输入MIPI RGB16/18/24/30/36bpp、YUV422 16/20/24bp…

在WSL2中安装多个Ubuntu实例

参考&#xff1a;How to install multiple instances of Ubuntu in WSL2 本文主要内容 第一步&#xff1a;在 WSL2 中安装最新的 Ubuntu第二步&#xff1a;下载适用于 WSL2 的 Ubuntu 压缩包第三步&#xff1a;在 WSL2 中安装第二个 Ubuntu 实例第四步&#xff1a;登录到第二个…

基于STC12C5A60S2系列1T 8051单片机SPI通信应用

基于STC12C5A60S2系列1T 8051单片机SPI通信应用 STC12C5A60S2系列1T 8051单片机管脚图STC12C5A60S2系列1T 8051单片机I/O口各种不同工作模式及配置STC12C5A60S2系列1T 8051单片机I/O口各种不同工作模式介绍STC12C5A60S2系列1T 8051单片机SPI通信介绍STC12C5A60S2系列1T 8051单片…

APISpace IP归属地查询接口案例代码

1.IP归属地查询API 1.1 API接口简介 IP归属地查询API&#xff1a;根据IP地址查询归属地信息&#xff0c;包含国家、省、市、区县和运营商等信息。APISpace 提供了IPv4 和 IPv6 的IP归属地查询接口&#xff0c;并且包含了各种归属地精度查询的接口。 1.2 IPv4 IPv4归属地查询…

星乐园项目┃助学无止境·探访暖人心

2023年7月10日至10月31日&#xff0c;广州市从化区齐家社会工作服务中心的“星乐园-乡村儿童公益辅导服务项目”社工带领高校志愿老师、社区志愿者在从化区城郊街新开村、太平镇西湖村分阶段、分批次对两个助学点的学困儿童家庭开展了入户探访活动。旨在通过走访了解他们的生活…

HTTP-HTTPS区别详解

一、HTTP协议 1. GET和POST的请求的区别 Post 和 Get 是 HTTP 请求的两种方法&#xff0c;其区别如下&#xff1a; 应用场景&#xff1a; GET 请求是一个幂等的请求&#xff0c;一般 Get 请求用于对服务器资源不会产生影响的场景&#xff0c;比如说请求一个网页的资源。而 Po…

Java算法(四):index方法的实现 查找一个元素是否存在数组中,并且返回相应的结果(考虑重复的情况)

Java算法&#xff1a;&#xff08;四&#xff09; 文章目录 Java算法&#xff1a;&#xff08;四&#xff09;一、查找元素在数组中的索引二、考虑数组中有多个重复的情况 一、查找元素在数组中的索引 需求&#xff1a; 设计一个方法&#xff0c;查找元素在数组中的索引位置 …

pytest中的pytest.ini

[pytest] filterwarnings ignore::DeprecationWarning addopts -v -s markers uat:1 smok:2 log_cli1 xfail_strict True filterwarnings ignore::DeprecationWarning 这个的功能就是 test_login.py::Test_login::test_login_correct_password PASSEDwarnings summary …