qt+opengl 三维坐标系(三)

news2024/12/30 2:06:34

文章目录

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


前言

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


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

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

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();
}

总结

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

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

avue 表单自定义标题无效问题(avue 表单自定义标题,当prop含有大写字母时失效)

问题描述&#xff1a; avue 表单自定义标题&#xff0c;官方文档可以生效&#xff0c;项目中不生效。多方排查发现&#xff0c;当prop含有大写字母时失效。 代码展示&#xff1a; <avue-form v-model"form" :option"option"><template #classN…

腾讯待办关停后,导出的ics文件怎么导入iPhone手机日历?

ics文件是一种通用日历格式保存的日历文件&#xff0c;不少提醒待办类软件支持导出ics文件&#xff0c;诸如大家所熟知的腾讯待办就支持导出成ics文件&#xff0c;腾讯待办的主要功能一待办事项和日程管理位置&#xff0c;设置好时间提醒后&#xff0c;可通过公众号端口弹出提醒…

c语言练习第10周(1~5)

根据公式求和 输入样例20输出样例 534.188884 #include<stdio.h> #include<math.h> int main() {int i,n;scanf("%d", &n);double s 0,t0;for (i 1; i < n; i) {t t sqrt(i);s s t;}printf("%.6lf", s);return 0; } 第一行输入…

STA——绪论

一、概述 静态时序分析&#xff08;简称STA&#xff09;是用来验证数字设计时序的技术之一&#xff0c;另外一种验证时序的方法是时序仿真&#xff0c;时序仿真可以同时验证功能和时序。“时序分析”这个术语就是用来指代“静态时序分析“或”时序仿真“这两种方法之一&#xf…

uniapp H5预览PDF支持手势缩放、分页、添加水印、懒加载、PDF下载

效果预览 项目说明 uniapp vue2 node&#xff1a;v14.18.3 npm&#xff1a; 6.14.15 安装pdfh5.js插件 pdfh5 - npm (npmjs.com)pdfh5.js 基于pdf.js和jQuery pdfh5 - npm (npmjs.com) npm install pdfh5 由于我安装最新的pdfh5.js后运行时报错 所以我选择降低版本,可能是node…

太细了:美团一面连环夺命20问,搞定就60W起

说在前面 在40岁老架构师尼恩的&#xff08;50&#xff09;读者社群中&#xff0c;经常有小伙伴&#xff0c;需要面试美团、京东、阿里、 百度、头条等大厂。 下面是一个小伙伴成功拿到通过了美团一面面试&#xff0c;现在把面试真题和参考答案收入咱们的宝典。 通过美团一面…

获取代码中所有的中文并排除注释代码

在给项目添加国际化适配时&#xff0c;需要吧代码中的中文一个一个替换成 $t(xxx.xxx) 这种代码&#xff0c;但是一个一个找中文比较麻烦&#xff0c;而且容易遗漏&#xff0c;于是就有了下面的代码&#xff0c;可以快速的帮我们找出对应文件中的所有中文&#xff0c;并且把中文…

链表OJ——环形链表初阶与进阶

呀哈喽&#xff0c;我是结衣。 环形链表1 描述 给你一个链表的头节点 head &#xff0c;判断链表中是否有环。 如果链表中有某个节点&#xff0c;可以通过连续跟踪 next 指针再次到达&#xff0c;则链表中存在环。 为了表示给定链表中的环&#xff0c;评测系统内部使用整数…

安防监控EasyCVR视频汇聚平台无法接入Ehome5.0是什么原因?该如何解决?

视频云存储/安防监控EasyCVR视频汇聚平台基于云边端智能协同&#xff0c;支持海量视频的轻量化接入与汇聚、转码与处理、全网智能分发、视频集中存储等。安防平台EasyCVR拓展性强&#xff0c;视频能力丰富&#xff0c;具体可实现视频监控直播、视频轮播、视频录像、云存储、回放…

TensorFlow学习笔记--(1)张量的随机生成

张量的生成 如何判断一个张量的维数&#xff1a;看张量的中括号有几层 0 1 2 &#xff1a;零维数列 [2 4 6] : 一维向量 [ [1 2 3] [4 5 6] ] : 二维数组 两行三列 第一行数据为 1 2 3 第二行数据为 4 5 6 以此类推 n维张量有n层中括号 tf.zeros(%指定一个张量的维数%) 生成一…

通用型 SPI-Flash 相关知识汇总(w25q16\q64,gd25q128\q256)

目录 管脚定义&#xff1a; 常用指令&#xff1a; GD25q16: gd25Q28 ​编辑 gw25q16 ​编辑 芯片丝印说明&#xff1a; GD系列&#xff1a; winbond系列&#xff1a; Read Identification&#xff08;9FH&#xff09;&#xff1a; 常见ID&#xff1a; GD: ​编辑…

class类默认导出,header字段在请求中的位置

这是封装好的&#xff0c;没封装的如下 如果没有用uni.post那么就是如下的结构 let header {Content-Type: application/x-www-form-urlencoded,tenant: MDAwMA, } request({url:/sal/formula/validFormula,method:post,data:{},header })

react-native技术难点与亮点

记录在数字化矿山 react-native App项目上所使用的知识点 文章目录 1&#xff0c;宽度自适应表格实现2&#xff0c;新建作业手势动画3&#xff0c;tabBar中间midTabBar动态展示4&#xff0c;堆料图实现5&#xff0c;语音识别实现6&#xff0c;待办事项上下滑动切换页面7&#x…

2022最新版-李宏毅机器学习深度学习课程-P46 自监督学习Self-supervised Learning(BERT)

一、概述&#xff1a;自监督学习模型与芝麻街 参数量 ELMO&#xff1a;94MBERT&#xff1a;340MGPT-2&#xff1a;1542MMegatron&#xff1a;8BT5&#xff1a;11BTuring NLG&#xff1a;17BGPT-3&#xff1a;175BSwitch Transformer&#xff1a;1.6T 二、Self-supervised Lear…

leaflet 地图遮罩、扣洞

leaflet地图遮罩效果图 主要代码&#xff1a; geojson也在下面 地图加载完成之后直接调用 drawBoundaryMask 方法 //边界高亮及遮罩效果drawBoundaryMask() {L.geoJSON(this.geojson, {style: function(feature) {return {fillColor: #000,stroke: #CD853F,fillOpacity: 0.4,c…

如何导出PPT画的图为高清图片?插入到world后不压缩图像的设置方法?

期刊投稿的时候&#xff0c;需要图片保持一定的清晰度数&#xff0c;那么我们怎么才能从PPT中导出符合要求的图片呢&#xff1f; 对于矢量图绘图软件所画的图&#xff0c;直接导出即可。 而PPT导出的图片清晰度在60pi&#xff0c;就很模糊。 整体思路&#xff1a; PPT绘图——…

国内做的好的工业RFID品牌有哪些?

随着数字化转型与智能制造的深入推荐&#xff0c;工业自动化程度的不断提高&#xff0c;RFID技术作为重要的自动识别与数据采集技术&#xff0c;越来越多地应用在工业领域。国内涉及RFID领域的企业也越来越多&#xff0c;那么&#xff0c;国内做的好的工业RFID品牌有那些呢&…

基于SpringBoot+Vue的招生管理系统

基于springbootvue的招生管理系统~ 开发语言&#xff1a;Java数据库&#xff1a;MySQL技术&#xff1a;SpringBootMyBatisVue工具&#xff1a;IDEA/Ecilpse、Navicat、Maven 系统展示 主页 专业信息 登录界面 管理员界面 录取通知管理 专业报名管理 摘要 基于Spring Boot和V…

安防监控系统视频融合平台EasyCVR页面地图功能细节详解

安防监控视频汇聚平台EasyCVR可拓展性强、视频能力灵活、部署轻快&#xff0c;可支持的主流标准协议有国标GB28181、RTSP/Onvif、RTMP等&#xff0c;以及支持厂家私有协议与SDK接入&#xff0c;包括海康Ehome、海大宇等设备的SDK等。平台既具备传统安防视频监控的能力&#xff…

原始手工配置ipsec vpn案例

两边防火墙的基本配置&#xff1a; 接口加入到对应的zone 默认路由 ip route-static 0.0.0.0 0 1.1.1.2 保证公网能ping通 抓包