OpenGL实现场景编辑器

文章目录

最近在使用Qt+OpenGL+glew+freeimage实现一个简单的场景编辑器,先看看他的功能是什么。

这个是软件的界面,中间的widget是用来显示场景的,左侧上方的dockwidget可以拖动模型到显示场景中,左侧下方是dockwidget是用于显示场景中加载的模型列表,右侧的dockwidget是用于显示当前模型的信息,包括他的位置和缩放比例。选中模型后会显示他的包围盒,左侧上方点击scene Action可以切换模式,是拖动场景还是拖动模型,拖动模型的时候会将所有显示包围盒的模型都进行移动。

界面模块

界面是使用QMainWindow,渲染界面是继承于QWidget,左右两侧有三个QDockWidget,左侧上方是一个QListWidget,用于显示所有xlm模型,左侧下方是一个QTreeWidget,用于显示所有在场景中的模型列表,右侧是用于显示模型的属性,这个是从Qt源码中拿出来的QtTreePropertyBrowser类。

QtListWidget.h

cpp 复制代码
#ifndef QTLISTWIDGET_H
#define QTLISTWIDGET_H

#include <QListWidget>
#include "rapidxml.hpp"

class QtListWidget : public QListWidget
{
    Q_OBJECT

public:
    QtListWidget(QWidget *parent = 0);

    virtual ~QtListWidget();

    virtual void load(const char* xmlFile);

protected:
    void dragEnterEvent(QDragEnterEvent *event);
    void dragMoveEvent(QDragMoveEvent *event);
    void dropEvent(QDropEvent *event);
    void startDrag(Qt::DropActions supportedActions);
};

#endif

QtListWidget.cpp

cpp 复制代码
#include <QtGui>

#include "QtListWidget.h"

QtListWidget::QtListWidget(QWidget *parent)
    : QListWidget(parent)
{
    setDragEnabled(true);
    setViewMode(QListView::IconMode);
    setSpacing(10);
    setAcceptDrops(false);
    setDropIndicatorShown(true);

    //for (int i = 0; i < 10; ++i)
    //{
    //    QString path = QCoreApplication::applicationDirPath();
    //    QPixmap bmp;
    //    bmp.load(path + "/images/example.jpg");

    //    QListWidgetItem* item = new QListWidgetItem(this);
    //    item->setIcon(QIcon(bmp));
    //    item->setData(Qt::UserRole, QVariant(bmp));
    //    item->setData(Qt::UserRole + 1, QPoint(1, 1));
    //    item->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsDragEnabled);
    //    item->setText("test-icon");
    //}
}


QtListWidget::~QtListWidget()
{

}


void QtListWidget::dragEnterEvent(QDragEnterEvent *event)
{
    if (event->mimeData()->hasFormat("image/x-puzzle-piece"))
    {
        event->accept();
    }
    else
    {
        event->ignore();
    }
}

void QtListWidget::dragMoveEvent(QDragMoveEvent *event)
{
    if (event->mimeData()->hasFormat("image/x-puzzle-piece")) 
    {
        event->setDropAction(Qt::MoveAction);
        event->accept();
    } 
    else
    {
        event->ignore();
    }
}

void QtListWidget::dropEvent(QDropEvent *event)
{
    event->ignore();
}

void QtListWidget::startDrag(Qt::DropActions /*supportedActions*/)
{
    QListWidgetItem *item = currentItem();

    QByteArray  itemData;
    QDataStream dataStream(&itemData, QIODevice::WriteOnly);
    QPixmap     pixmap      =   qvariant_cast<QPixmap>(item->data(Qt::UserRole));
    QString     location = item->data(Qt::UserRole + 1).toString();

    dataStream << location;

    QMimeData*  mimeData    =   new QMimeData;
    mimeData->setData("image/x-puzzle-piece", itemData);

    QDrag*  drag    =   new QDrag(this);
    drag->setMimeData(mimeData);
    drag->setHotSpot(QPoint(pixmap.width()/2, pixmap.height()/2));
    drag->setPixmap(pixmap);

    drag->exec(Qt::MoveAction);
}

void QtListWidget::load( const char* xmlFile )
{
    FILE*   pFile   =   fopen(xmlFile,"rb");
    if (pFile == 0)
    {
        return;
    }
    fseek(pFile,0,SEEK_END);
    size_t  len =   ftell(pFile);
    fseek(pFile,0,SEEK_SET);

    char*   xml    =   new char[len + 1];
    fread(xml,1,len,pFile);
    xml[len]   =   0;
    fclose(pFile);
    try
    {
        rapidxml::xml_document<>    doc;
        doc.parse<0>(xml);

        rapidxml::xml_node<>* root = doc.first_node();
        rapidxml::xml_node<>* model = root->first_node();
        for (; model != 0; model = model->next_sibling())
        {
            rapidxml::xml_attribute<>* attrIcon = model->first_attribute("icon");
            rapidxml::xml_attribute<>* attrFile = model->first_attribute("file");
            rapidxml::xml_attribute<>* attrText = model->first_attribute("name");

            QString bmpPath = QCoreApplication::applicationDirPath() + "/" + attrIcon->value();

            QPixmap bmp;
            bmp.load(bmpPath);

            QListWidgetItem* item = new QListWidgetItem(this);
            item->setIcon(QIcon(bmp));
            item->setData(Qt::UserRole, QVariant(bmp));
            item->setData(Qt::UserRole + 1, attrFile->value());
            item->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsDragEnabled);
            item->setText(attrText->value());
        }
    }
    catch (...)
    {
    }
    delete  []xml;
}

渲染模块

渲染模块主要是通过glew库和Qt的定时器来实现的。

cpp 复制代码
connect(&_renderTimer,&QTimer::timeout,this, &OGLWidget::render);

这个项目加载的模型是用的xml格式。

目前这个项目使用的固定管线来进行渲染的,所有没有写着色器。首先说一下记载纹理是用的FreeImage库

cpp 复制代码
unsigned Scene::createTextureFromImage( const char* fileName )
{
    //1 获取图片格式
    FREE_IMAGE_FORMAT       fifmt   =   FreeImage_GetFileType(fileName, 0);
    if (fifmt == FIF_UNKNOWN)
    {
        return  0;
    }
    //2 加载图片
    FIBITMAP*               dib     =   FreeImage_Load(fifmt, fileName,0);

    FREE_IMAGE_COLOR_TYPE   type    =   FreeImage_GetColorType(dib);

    //! 获取数据指针
    FIBITMAP*   temp    =   dib;
    dib =   FreeImage_ConvertTo32Bits(dib);
    FreeImage_Unload(temp);

    BYTE*   pixels =   (BYTE*)FreeImage_GetBits(dib);
    int     width   =   FreeImage_GetWidth(dib);
    int     height  =   FreeImage_GetHeight(dib);

    for (int i = 0 ;i < width * height * 4 ; i+=4 )
    {
        BYTE temp       =   pixels[i];
        pixels[i]       =   pixels[i + 2];
        pixels[i + 2]   =   temp;
    }

    unsigned    res =   createTexture(width,height,pixels);
    FreeImage_Unload(dib);
    return      res;
}
unsigned Scene::createTexture( int w,int h,const void* data )
{
    unsigned    texId;
    glGenTextures(1,&texId);
    glBindTexture(GL_TEXTURE_2D,texId);
    glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);
    glTexImage2D(GL_TEXTURE_2D,0,GL_RGBA,w,h,0,GL_RGBA,GL_UNSIGNED_BYTE,data);

    return  texId;
}
cpp 复制代码
    V3U2    vertexs[]   =   
    {
        {-1, 0,   1,  0,  0},
        { 1, 0,   1,  1,  0},
        { 1, 0,  -1,  1,  1},

        {-1, 0,   1,  0,  0},
        { 1, 0,  -1,  1,  1},
        {-1, 0,  -1,  0,  1},
    };

    for (int i = 0 ;i  < 6 ; ++ i)
    {
        vertexs[i].x    *=   100;
        vertexs[i].z    *=   100;

        vertexs[i].u    *=   10;
        vertexs[i].v    *=   10;
    }

    glBindTexture(GL_TEXTURE_2D,_texGround);

    glEnableClientState(GL_VERTEX_ARRAY);
    glEnableClientState(GL_TEXTURE_COORD_ARRAY);

    glVertexPointer(3,GL_FLOAT,sizeof(V3U2),&vertexs[0].x);
    glTexCoordPointer(2,GL_FLOAT,sizeof(V3U2),&vertexs[0].u);

    glDrawArrays(GL_TRIANGLES, 0, 6 );

    glDisableClientState(GL_VERTEX_ARRAY);
    glDisableClientState(GL_TEXTURE_COORD_ARRAY);

这个是渲染地面的代码,只需要传入顶点数据,纹理坐标和纹理即可。

再说一下模型的渲染在当前项目中的架构:

首先有有一个基类CELLObject,里面记录了名称和一些用户自定义的数据。

cpp 复制代码
#pragma once

namespace   CELL
{
    class   CELLObject
    {
    protected:
        void*   _user;
        char _name[64];
    public:
        CELLObject(void* user = 0)
            :_user(user)
        {}

        virtual ~CELLObject()
        {}

        void    setUserData(void* user)
        {
            _user   =   user;
        }

        void*   getUserData()
        {
            return  _user;
        }

        char* getName()
        {
            return _name;
        }

        void setName(char* name)
        {
            strncpy(_name, name, sizeof(_name));
        }
    };
}

负责管理节点的是CELLNode类,里面存储了模型的一些信息,包括位置,缩放,旋转信息,包围盒,可绘制体。

cpp 复制代码
#pragma once

#include "CELLMath.hpp"
#include "CELLRenderable.hpp"
#include "glew/glew.h"

namespace   CELL
{
    class   CELLNode  :public CELLObject
    {
    public:
        enum
        {
            FLAG_NORMAL = 1 << 0,
            FLAG_SELECT = 1 << 1,
            FLAG_VISIBLE = 1 << 2,
            FLAG_UPDATE = 1 << 3,
        };

    public:
        CELL::quatr         _quat;
        CELL::real3         _scale;
        CELL::real3         _trans;
        CELL::aabb3d        _aabb;
        CELL::matrix4       _local;
        CELLRenderable*     _renderable;
        unsigned _flag;
        
    public:
        CELLNode()
        {
            _flag = FLAG_NORMAL | FLAG_VISIBLE;
            _scale  =   float3(1,1,1);
            _quat   =   angleAxis(real(60),real3(0,1,0));
            _local  =   makeTransform(_trans,_scale,_quat);
            sprintf(_name, "CELLNode-%p", this);
        }
        virtual ~CELLNode()
        {}

        inline  void    setFlag(unsigned flag)
        {
            _flag |= flag;
        }
        inline  void    removeFlag(unsigned flag)
        {
            _flag &= ~flag;
        }
        inline  bool    hasFlag(unsigned flag)
        {
            return  _flag & flag ? true : false;
        }

        inline  void    setAabb(const aabb3d& aabb)
        {
            _aabb   =   aabb;
        }
        inline  aabb3d  getAabb()
        {
            return  _aabb;
        }

        inline CELL::matrix4 getLocal()
        {
            return _local;
        }

        inline void setLocal(CELL::matrix4 matrix)
        {
            _local = matrix;
        }

        inline  void    setScale(const float3& scale)
        {
            _scale  =   scale;
        }
        inline  float3  getScale()
        {
            return  _scale;
        }

        inline float3 getTrans()
        {
            return _trans;
        }

        inline  void    setTranslation(const float3& trans)
        {
            _trans  =   trans;
        }

        inline  void    setQuat(const quatr& quat)
        {
            _quat   =   quat;
        }

        inline  quatr    getQuat()const
        {
            return _quat;
        }

        inline  void    setAngle(float angle,float3 axis = float3(0,1,0))
        {
            _quat   =   CELL::angleAxis(angle,axis);
        }

        inline void attach(CELLRenderable* render)
        {
            _renderable = render;
        }

        inline CELL::CELLRenderable* getAttach()const
        {
            return _renderable;
        }

        inline void update()
        {
            _local = makeTransform(_trans, _scale, _quat);
        }

        inline void render(CELLCamera& camera)
        {
            CELL::matrix4 vp = camera.getProject() * camera.getView();

            CELL::matrix4 mvp = vp * _local;
            glLoadMatrixf(mvp.data());
            _renderable->render(this);

            if(_flag & FLAG_SELECT)
                renderAabb(vp);
        }

        inline void renderAabb(CELL::matrix4 &vp)
        {
            float3          vertex[8];
            aabb3d  aabb = _aabb;
            aabb.transform(_local);
            aabb.getAllCorners(vertex);

            unsigned char   index[] =
            {
                0,1,    1,2,    2,3,    3,0,
                4,5,    5,6,    6,7,    7,4,
                1,5,    2,4,    0,6,    3,7,
            };

            glLoadMatrixf(vp.data());
            glBindTexture(GL_TEXTURE_2D, 0);
            glColor3f(1, 0, 0);

            glEnableClientState(GL_VERTEX_ARRAY);
            glVertexPointer(3, GL_FLOAT, sizeof(float3), vertex);
            glDrawElements(GL_LINES, sizeof(index), GL_UNSIGNED_BYTE, index);

            glDisableClientState(GL_VERTEX_ARRAY);

            glColor3f(1, 1, 1);
        }
    };
}

最后负责渲染的是CELLRenderable类,其中有两个虚函数需要子类实现

cpp 复制代码
        virtual bool    load(const char* fileName)
        {
            return  false;
        }
        /**
        *   绘制函数
        */
        virtual void    render(CELLNode*)
        {}

这两个函数分别是加载函数和渲染函数,由于目前是使用xml格式的数据进行渲染,所以实现了CELLModelXML类。

cpp 复制代码
#pragma once

#include <vector>
#include "rapidxml.hpp"
#include "CELLRenderable.hpp"
#include "CELLFileReader.hpp"
#include "CELLTextureMgr.hpp"
#include <QString>
#include <QCoreApplication>

namespace   CELL
{
    class   CELLModelXML :public CELLRenderable
    {
    public:
        struct  V3N3U2
        {
            float   x,y,z;
            float   nx,ny,nz;
            float   u,v;
        };

        struct  Face
        {
            short   x,y,z;
        };
        struct  Primitive
        {
            int     start;
            int     count;
            int     mode;
        };
        typedef std::vector<V3N3U2>     ArrayVertex;
        typedef std::vector<Face>       ArrayFace;
        typedef std::vector<Primitive>  ArrayPri;
    private:
        CELLModelXML(const CELLModelXML&);
        CELLModelXML& operator = (const CELLModelXML& rights);
    public:
        ArrayVertex _vertexs;
        ArrayFace   _faces;
        ArrayPri    _primitive;
        unsigned    _texture;
    public:
        CELLModelXML()
        {}
        virtual ~CELLModelXML()
        {}
        /**
        *   加载文件
        */
        virtual bool    load(const char* fileName)
        {
            CELLFileReader  file(fileName);
            if (file.isBad())
            {
                return  false;
            }
            file.readAll();
            try
            {
                rapidxml::xml_document<>    doc;
                rapidxml::xml_node<>*	    rootNode    =   0;
                char*                       xmlData     =   (char*)file.data();
                doc.parse<0>(xmlData); 
                rootNode    =	doc.first_node("mesh");
                if (rootNode == 0)
                {
                    return  false;
                }
                rapidxml::xml_node<>*       node        =   rootNode->first_node();
                for ( ; node != 0 ; node = node->next_sibling())
                {
                    if(strcmp(node->name(),"vertex") == 0)
                    {
                        parseVertexs(node);
                    }
                    else if(strcmp(node->name(),"face") == 0)
                    {
                        parseFace(node);
                    }
                    else if(strcmp(node->name(),"primitive") == 0)
                    {
                        parsePrimitive(node);
                    }
                }
                return  true;
            }
            catch (...)
            {
                return  false;
            }
        }

        virtual void render(CELLNode*)
        {
            glBindTexture(GL_TEXTURE_2D, _texture);

            glEnableClientState(GL_VERTEX_ARRAY);
            glEnableClientState(GL_TEXTURE_COORD_ARRAY);
            glVertexPointer(3,GL_FLOAT,         sizeof(V3N3U2),     &_vertexs[0].x);
            glTexCoordPointer(2,GL_FLOAT,       sizeof(V3N3U2),     &_vertexs[0].u);

            glDrawElements(GL_TRIANGLES,_faces.size() * 3,GL_UNSIGNED_SHORT,&_faces[0]);

            glDisableClientState(GL_VERTEX_ARRAY);
            glDisableClientState(GL_TEXTURE_COORD_ARRAY);
        }
    protected:

        /**
        *   内部调用,主要用来计算包围盒大小
        */
        void    updateAabb()
        {
            float3  vMin   =   float3(FLT_MAX,FLT_MAX,FLT_MAX);
            float3  vMax   =   float3(-FLT_MAX,-FLT_MAX,-FLT_MAX);

            for (size_t i = 0 ;i < _vertexs.size() ; ++i)
            {
                V3N3U2&     pData   =   _vertexs[i];

                vMin.x =   tmin(pData.x,vMin.x);
                vMin.y =   tmin(pData.y,vMin.y);
                vMin.z =   tmin(pData.z,vMin.z);

                vMax.x =   tmax(pData.x,vMax.x);
                vMax.y =   tmax(pData.y,vMax.y);
                vMax.z =   tmax(pData.z,vMax.z);
            }
            _aabb.setExtents(vMin,vMax);
        }
        
        /**
        *   解析顶点信息
        */
        virtual void parseVertexs( rapidxml::xml_node<>* node )
        {
            rapidxml::xml_attribute<>*  attrCount   =   node->first_attribute("count");
            rapidxml::xml_attribute<>*  attrSizeOf  =   node->first_attribute("size");
            rapidxml::xml_node<>*       cNode       =   0;
            const char*                 pValue      =   0;
            unsigned                    dataSize    =   0;

            if (attrCount == 0 || attrSizeOf == 0)
            {
                return;
            }

            int dataCount   =   atoi(attrCount->value());
            if (dataCount == 0)
            {
                return;
            }
            _vertexs.resize(dataCount);
            cNode       =   node->first_node();

            for ( int i = 0; cNode && i < dataCount; cNode = cNode->next_sibling(), ++ i)
            {
                V3N3U2& vertex  =   _vertexs[i];
                        pValue  =   cNode->value();
                sscanf(
                    pValue
                    ,"%f %f %f %f %f %f %f %f"
                    ,&vertex.x
                    ,&vertex.y
                    ,&vertex.z
                    ,&vertex.nx
                    ,&vertex.ny
                    ,&vertex.nz
                    ,&vertex.u
                    ,&vertex.v
                    );
            }

            updateAabb();
        }
        /**
        *   解析面信息
        */
        virtual void parseFace( rapidxml::xml_node<>* node )
        {
            rapidxml::xml_attribute<>*  attrType    =   node->first_attribute("type");
            rapidxml::xml_attribute<>*  attrCount   =   node->first_attribute("count");
            rapidxml::xml_attribute<>* attrTexture =    node->first_attribute("texture");
            rapidxml::xml_node<>*       cNode       =   node->first_node();

            if (attrType == 0 || attrCount== 0 || cNode    == 0)
            {
                return;
            }
            int dataCount   =   atoi(attrCount->value());

            if (dataCount == 0)
            {
                return;
            }
            if (attrTexture)
            {
                const char* pTextureFile = attrTexture->value();
                QString path = QCoreApplication::applicationDirPath() + "/" + pTextureFile;
                _texture = CELLTextureMgr::instance().getTexture(path.toLocal8Bit());
            }
            _faces.resize(dataCount);

            for ( int i = 0 ; cNode && i < dataCount ; cNode = cNode->next_sibling(), ++ i)
            {
                int     r,g,b;
                Face&   face    =   _faces[i];
                sscanf(cNode->value(),"%d %d %d",&r,&g,&b);
                face.x =   (unsigned short)(r);
                face.y =   (unsigned short)(g);
                face.z =   (unsigned short)(b);
            }
        }
        /**
        *   解析绘制的图元组
        */
        virtual void parsePrimitive( rapidxml::xml_node<>* node )
        {
            rapidxml::xml_attribute<>*  attrCount   =   node->first_attribute("count");
            rapidxml::xml_node<>*       cNode       =   node->first_node();
            if (attrCount == 0 || cNode == 0)
            {
                return;
            }
            int priCount    =   atoi(attrCount->value());
         
            _primitive.resize(priCount);

            for (int i = 0 ;cNode; cNode = cNode->next_sibling(), ++ i )
            {
                rapidxml::xml_attribute<>*  attType     =   cNode->first_attribute("type");
                rapidxml::xml_attribute<>*  attStart    =   cNode->first_attribute("start");
                rapidxml::xml_attribute<>*  attCount    =   cNode->first_attribute("count");
                if (attType == 0 || attStart == 0 || attCount == 0)
                {
                    continue;
                }
                _primitive[i].mode  =   atoi(attType->value());
                _primitive[i].start =   atoi(attStart->value());
                _primitive[i].count =   atoi(attCount->value());
            }
        }
    };
}

这个类的作用是从xml文件读取出顶点数据,纹理坐标数据,纹理路径,顶点索引数据,根据这些信息进行渲染。

交互模块

交互模块主要指的是用户操作后鼠标,键盘事件后场景进行对应的操作。

cpp 复制代码
    virtual void dragEnterEvent(QDragEnterEvent *evt);

    virtual void dragMoveEvent(QDragMoveEvent *evt);

    virtual void dropEvent(QDropEvent *evt);

    /**
    *   窗口大小变化事件
    */
    virtual void resizeEvent(QResizeEvent *evt);

    /**
    *   鼠标按下
    */
    virtual void mousePressEvent(QMouseEvent*evt);

    virtual void mouseReleaseEvent(QMouseEvent*evt);

    virtual void mouseDoubleClickEvent(QMouseEvent*evt);

    virtual void mouseMoveEvent(QMouseEvent*evt);

    virtual void wheelEvent(QWheelEvent* evt);

首先先说一下实现的摄像机。

cpp 复制代码
#pragma once

#include    "CELLMath.hpp"

namespace   CELL
{
    class CELLCamera
    {
    public:
        float3      _eye;
        float3      _up;
        float3      _right;
        float3      _target;
        float3      _dir;
        matrix4     _matView;
        matrix4     _matProj;
        matrix4     _matWorld;
        float2      _viewSize;
    public:
        CELLCamera(const float3& target = float3(0,0,0),const float3& eye = float3(0,100,100),const float3& right = float3(1,0,0))
        {
            _viewSize   =   float2(256,256);
            _matView    =   CELL::matrix4(1);
            _matProj    =   CELL::matrix4(1);
            _matWorld   =   CELL::matrix4(1);

            _target     =   target;
            _eye        =   eye;
            _dir        =   normalize(_target - _eye);
            _right      =   right;
            _up         =   normalize(cross(_right,_dir));

        }
        ~CELLCamera()
        {}

        float3 getEye() const 
        { 
            return _eye;
        }
        /**
        *   设置眼睛的位置
        */
        void    setEye(CELL::float3 val)
        { 
            _eye    =   val; 
        }
        
        float3 getTarget() const 
        { 
            return _target;
        }

        void    setTarget(CELL::float3 val) 
        { 
            _target = val;
        }
        void    setRight(CELL::float3 val)
        {
            _right  =   val;
        }
        
        float3 getUp() const 
        { 
            return _up;
        }
        void    setUp(CELL::float3 val)
        {
            _up = val;
        }
        float3  getDir() const
        {
            return  _dir;
        }

        float3  getRight() const
        {
            return  _right;
        }
        void    setViewSize(const float2& viewSize)
        {
            _viewSize   =   viewSize;
        }
        void    setViewSize(float x,float y)
        {
            _viewSize   =   float2(x,y);
        }

        float2  getViewSize()
        {
            return  _viewSize;
        }
        
        void    setProject(const matrix4& proj)
        {
            _matProj    =   proj;
        }
        const matrix4& getProject() const
        {
            return  _matProj;
        }
        const matrix4&  getView() const
        {
            return  _matView;
        }

        /**
        *   正交投影
        */
        void    ortho( float left, float right, float bottom, float top, float zNear, float zFar )
        {
            _matProj    =   CELL::ortho(left,right,bottom,top,zNear,zFar);
        }
        /**
        *   透视投影
        */
        void    perspective(float fovy, float aspect, float zNear, float zFar)
        {
            _matProj    =   CELL::perspective<float>(fovy,aspect,zNear,zFar);
        }

         /**
        *   世界坐标转化为窗口坐标
        */
        bool    project( const float4& world, float4& screen )
        {
            screen  =   (_matProj * _matView * _matWorld) * world;
            if (screen.w == 0.0f)
            {
                return false;
            }
            screen.x    /=  screen.w;
            screen.y    /=  screen.w;
            screen.z    /=  screen.w;

            // map to range 0 - 1
            screen.x    =   screen.x * 0.5f + 0.5f;
            screen.y    =   screen.y * 0.5f + 0.5f;
            screen.z    =   screen.z * 0.5f + 0.5f;

            // map to viewport
            screen.x    =   screen.x * _viewSize.x;
            screen.y    =   _viewSize.y - (screen.y * _viewSize.y);
            return  true;
        }


        /**
        *   世界坐标转化为窗口坐标
        */
        float2  worldToScreen( const float3& world)
        {
            float4  worlds(world.x,world.y,world.z,1);
            float4  screens;
            project(worlds,screens);
            return  float2(screens.x,screens.y);
        }

        /**
        *   窗口坐标转化为世界坐标
        */
        float3  screenToWorld(const float2& screen)
        {
            float4  screens(screen.x,screen.y,0,1);
            float4  world;
            unProject(screens,world);
            return  float3(world.x,world.y,world.z);
        }

        float3  screenToWorld(float x,float y)
        {
            float4  screens(x,y,0,1);
            float4  world;
            unProject(screens,world);
            return  float3(world.x,world.y,world.z);
        }


        /**
        *   窗口坐标转化为世界坐标
        */
        bool    unProject( const float4& screen, float4& world )
        {
            float4 v;
            v.x =   screen.x;
            v.y =   screen.y;
            v.z =   screen.z;
            v.w =   1.0;

            // map from viewport to 0 - 1
            v.x =   (v.x) /_viewSize.x;
            v.y =   (_viewSize.y - v.y) /_viewSize.y;
            //v.y = (v.y - _viewPort.Y) / _viewPort.Height;

            // map to range -1 to 1
            v.x =   v.x * 2.0f - 1.0f;
            v.y =   v.y * 2.0f - 1.0f;
            v.z =   v.z * 2.0f - 1.0f;

            CELL::matrix4  inverse = (_matProj * _matView * _matWorld).inverse();

            v   =   v * inverse;
            if (v.w == 0.0f)
            {
                return false;
            }
            world   =   v / v.w;
            return true;
        }

        Ray createRayFromScreen(int x,int y)
        {
            float4  minWorld;
            float4  maxWorld;

            float4  screen(float(x),float(y),0,1);
            float4  screen1(float(x),float(y),1,1);

            unProject(screen,minWorld);
            unProject(screen1,maxWorld);
            Ray     ray;
            ray.setOrigin(float3(minWorld.x,minWorld.y,minWorld.z));

            float3  dir(maxWorld.x - minWorld.x,maxWorld.y - minWorld.y, maxWorld.z - minWorld.z);
            ray.setDirection(normalize(dir));
            return  ray;
        }
        /**
        *   下面的函数的功能是将摄像机的观察方向绕某个方向轴旋转一定的角度 
        *   改变观察者的位置,目标的位置不变化
        */
        virtual void    rotateViewY(float angle) 
        { 
            _dir        =   rotateY<float>(_dir,    angle);
            _up         =   rotateY<float>(_up,     angle);
            _right      =   normalize(cross(_dir,_up));
            float   len =   length(_eye - _target);
            _eye        =   _target - _dir * len;
            _matView    =   CELL::lookAt(_eye,_target,_up);
        }

        virtual void    rotateViewX(float angle) 
        { 
            matrix4 mat(1) ;
            mat.rotate(angle,_right);
            _dir        =   _dir * mat;
            _up         =   _up * mat;
            _right      =   normalize(cross(_dir,_up));
            float   len =   length(_eye - _target);
            _eye        =   _target - _dir * len;
            _matView    =   CELL::lookAt(_eye,_target,_up);
        }

        virtual void    update()
        {
            _matView    =   CELL::lookAt(_eye,_target,_up);
        }
    };
}

这是摄像机的代码,其中定义了一些摄像机的基本信息,包括,眼睛的位置,视点位置,向上的向量,由这三个元素就可以确定一个摄像机的坐标系,里面还将投影矩阵进行维护。

下面说一下用户操作鼠标后模型是怎么进行变动的。首先是鼠标按下事件。

cpp 复制代码
void OGLWidget::mousePressEvent( QMouseEvent*evt )
{
    switch (evt->button())
    {
    case Qt::LeftButton:
        {
            _leftBtnFlag    =   true;
            _mousePos       =   QPoint(evt->x(),evt->y());

            if (_optionMode == 1)
            {
                m_pickObj = pickNode(evt->x(), evt->y());
            }
        }
        
        break;
    case Qt::RightButton:
        {
            _rightBtnFlag   =   true;
            _mousePos       =   QPoint(evt->x(),evt->y());
        }
        break;
    case Qt::MidButton:
        break;
    }
}

这里面分为鼠标左击和鼠标右击,鼠标左击主要是用于控制场景的移动,鼠标的右击用于控制场景的旋转,左击的_optionMode是用于控制是移动模型还是移动场景的标志位,由于需要在移动的时候知道是左键按下的还是右键按下的,所以需要有一个标志位来进行判断。

鼠标释放事件

cpp 复制代码
void OGLWidget::mouseReleaseEvent( QMouseEvent*evt )
{
    switch (evt->button())
    {
    case Qt::LeftButton:
        {
            _leftBtnFlag    =   false;
        }
        break;
    case Qt::RightButton:
        {
            _rightBtnFlag   =   false;
        }
        break;
    case Qt::MidButton:
        break;
    }
}

用于取消标志位,说明用户已经不进行操作场景。

鼠标移动事件

cpp 复制代码
void OGLWidget::mouseMoveEvent( QMouseEvent*evt )
{
    if (_leftBtnFlag)
    {
        if (_optionMode == 0)
        {
            CELL::Ray       ray0 = _camera.createRayFromScreen(_mousePos.x(), _mousePos.y());
            CELL::Ray       ray1 = _camera.createRayFromScreen(evt->x(), evt->y());

            CELL::float3    pos1 = calcIntersectPoint(ray0);
            CELL::float3    pos2 = calcIntersectPoint(ray1);

            _mousePos = QPoint(evt->x(), evt->y());

            CELL::float3    offset = pos1 - pos2;

            CELL::float3    newEye = _camera.getEye() + offset;
            CELL::float3    newTgt = _camera.getTarget() + offset;

            _camera.setEye(newEye);
            _camera.setTarget(newTgt);
            _camera.update();
        }
        else if (_optionMode == 1)
        {
            CELL::CELLNode* node = dynamic_cast<CELL::CELLNode*>(m_pickObj);
            if (node)
            {
                CELL::Ray       ray0 = _camera.createRayFromScreen(_mousePos.x(), _mousePos.y());
                CELL::Ray       ray1 = _camera.createRayFromScreen(evt->x(), evt->y());

                CELL::float3    pos1 = calcIntersectPoint(ray0);
                CELL::float3    pos2 = calcIntersectPoint(ray1);

                _mousePos = QPoint(evt->x(), evt->y());

                CELL::float3    offset = pos2 - pos1;

                for (iterator itr = begin(); itr != end(); ++itr)
                {
                    CELL::CELLNode* node = *itr;
                    if (node->hasFlag(CELL::CELLNode::FLAG_SELECT))
                    {

                        node->setTranslation(node->getTrans() + offset);
                    }
                }
            }
            else
            {
                CELL::Ray       ray0 = _camera.createRayFromScreen(_mousePos.x(), _mousePos.y());
                CELL::Ray       ray1 = _camera.createRayFromScreen(evt->x(), evt->y());

                CELL::float3    pos1 = calcIntersectPoint(ray0);
                CELL::float3    pos2 = calcIntersectPoint(ray1);

                float   xMin = CELL::tmin<float>(pos1.x, pos2.x);
                float   xMax = CELL::tmax<float>(pos1.x, pos2.x);

                float   zMin = CELL::tmin<float>(pos1.z, pos2.z);
                float   zMax = CELL::tmax<float>(pos1.z, pos2.z);

                float   yMin = 0;
                float   yMax = 9999999;

                CELL::aabb3d    aabbMouse;
                aabbMouse.setExtents(xMin, yMin, zMin, xMax, yMax, zMax);

                for (iterator itr = begin(); itr != end(); ++itr)
                {
                    CELL::CELLNode* node = *itr;
                    CELL::aabb3d    aabb = node->getAabb();
                    aabb.transform(node->getLocal());
                    if (aabbMouse.intersects(aabb))
                    {
                        node->setFlag(CELL::CELLNode::FLAG_SELECT);
                    }
                    else
                    {
                        node->removeFlag(CELL::CELLNode::FLAG_SELECT);
                    }
                }
            }

        }

    }
    else if(_rightBtnFlag)
    {
        QPoint  ptCur       =   QPoint(evt->x(),evt->y());
        QPoint  offset      =   ptCur - _mousePos;
                _mousePos   =   ptCur;
        _camera.rotateViewY(offset.x() * 0.3f);
        _camera.rotateViewX(offset.y() * 0.3f);
    }

}

先介绍一下鼠标右键移动的事件,主要是根据鼠标移动的位置,重新计算摄像机的摄像机矩阵,核心的思想就是保持观察者的位置不变,然后重新计算视点向量和向上的向量,再根据_eye = _target - _dir * len;计算出观察者的位置再重新计算摄像机矩阵后即可。

然后是鼠标左击事件,这里需要分为两部分来说,首先是移动场景的部分

cpp 复制代码
        if (_optionMode == 0)
        {
            CELL::Ray       ray0 = _camera.createRayFromScreen(_mousePos.x(), _mousePos.y());
            CELL::Ray       ray1 = _camera.createRayFromScreen(evt->x(), evt->y());

            CELL::float3    pos1 = calcIntersectPoint(ray0);
            CELL::float3    pos2 = calcIntersectPoint(ray1);

            _mousePos = QPoint(evt->x(), evt->y());

            CELL::float3    offset = pos1 - pos2;

            CELL::float3    newEye = _camera.getEye() + offset;
            CELL::float3    newTgt = _camera.getTarget() + offset;

            _camera.setEye(newEye);
            _camera.setTarget(newTgt);
            _camera.update();
        }

这里是通过射线求交的方式计算出与模型的交点后,根据上一次的交点和这一次的交点计算出一个偏移量后,重新计算摄像机的视点位置和摄像机的位置更新到摄像机中即可。

还有一种是根据鼠标左键移动模型

cpp 复制代码
else if (_optionMode == 1)
{
    CELL::CELLNode* node = dynamic_cast<CELL::CELLNode*>(m_pickObj);
    if (node)
    {
        CELL::Ray       ray0 = _camera.createRayFromScreen(_mousePos.x(), _mousePos.y());
        CELL::Ray       ray1 = _camera.createRayFromScreen(evt->x(), evt->y());

        CELL::float3    pos1 = calcIntersectPoint(ray0);
        CELL::float3    pos2 = calcIntersectPoint(ray1);

        _mousePos = QPoint(evt->x(), evt->y());

        CELL::float3    offset = pos2 - pos1;

        for (iterator itr = begin(); itr != end(); ++itr)
        {
            CELL::CELLNode* node = *itr;
            if (node->hasFlag(CELL::CELLNode::FLAG_SELECT))
            {

                node->setTranslation(node->getTrans() + offset);
            }
        }
    }
    else
    {
        CELL::Ray       ray0 = _camera.createRayFromScreen(_mousePos.x(), _mousePos.y());
        CELL::Ray       ray1 = _camera.createRayFromScreen(evt->x(), evt->y());

        CELL::float3    pos1 = calcIntersectPoint(ray0);
        CELL::float3    pos2 = calcIntersectPoint(ray1);

        float   xMin = CELL::tmin<float>(pos1.x, pos2.x);
        float   xMax = CELL::tmax<float>(pos1.x, pos2.x);

        float   zMin = CELL::tmin<float>(pos1.z, pos2.z);
        float   zMax = CELL::tmax<float>(pos1.z, pos2.z);

        float   yMin = 0;
        float   yMax = 9999999;

        CELL::aabb3d    aabbMouse;
        aabbMouse.setExtents(xMin, yMin, zMin, xMax, yMax, zMax);

        for (iterator itr = begin(); itr != end(); ++itr)
        {
            CELL::CELLNode* node = *itr;
            CELL::aabb3d    aabb = node->getAabb();
            aabb.transform(node->getLocal());
            if (aabbMouse.intersects(aabb))
            {
                node->setFlag(CELL::CELLNode::FLAG_SELECT);
            }
            else
            {
                node->removeFlag(CELL::CELLNode::FLAG_SELECT);
            }
        }
    }

}

这里也分为两个情况,如果这次点击的位置没有拾取到模型的话,就说明用户想要绘制一个区域来选中模型(目前没有实现绘制区域的矩形),根据这两个点生成一个包围盒,根据这个包围盒遍历所有的节点进行求交,有交点的话就将模型的标志位增加选中状态,如果拾取到模型的话就遍历节点如果是有选中状态的话就移动模型。

改变窗口大小事件

cpp 复制代码
void OGLWidget::resizeEvent( QResizeEvent *evt )
{
    glViewport(0,0,width(),height());
    /没有写这个导致的问题
    _camera.setViewSize(width(),height());
    _camera.perspective(60.0f,float(width())/float(height()),0.1f,10000.0f);
}

就是改变视口的大小和摄像机的投影矩阵就行。

联动模块

这个模块主要是要实现,当模型拖入场景后左侧下方的树需要增加模型的列表,还有当点击左侧下方的树后需要在右侧显示对应模型的属性。

cpp 复制代码
void OGLWidget::dropEvent( QDropEvent *evt )
{
    if (_frameWindow == nullptr)
    {
        return;
    }

    QString     strFileName;
    QByteArray  data    =   evt->mimeData()->data("image/x-puzzle-piece");
    QDataStream dataStream(&data, QIODevice::ReadOnly);
   
    dataStream >> strFileName;

    int iCnt = _frameWindow->getTreeWidget()->topLevelItemCount();
    QTreeWidgetItem* pFind = NULL;
    QTreeWidgetItem* pItem = NULL;

    for (int i = 0; i < iCnt; ++i)
    {
        QTreeWidgetItem* pTop = _frameWindow->getTreeWidget()->topLevelItem(i);
        QString             text = pTop->text(0);
        if (text == strFileName)
        {
            pFind = pTop;
            break;
        }
    }

    CELL::CELLNode* node = createNode(strFileName.toStdString().c_str(), evt->pos().x(), evt->pos().y());

    if (node == NULL)
    {
        return;
    }
    if (pFind)
    {
        pItem = new QTreeWidgetItem(pFind);
        pItem->setData(0, Qt::UserRole, QVariant((qlonglong)node));
        pItem->setText(0, node->getName());
    }
    else
    {
        pFind = new QTreeWidgetItem(_frameWindow->getTreeWidget());
        pFind->setText(0, strFileName);
        pItem = new QTreeWidgetItem(pFind);
        pItem->setData(0, Qt::UserRole, QVariant((qlonglong)node));
        pItem->setText(0, node->getName());
    }

    evt->accept();
}
cpp 复制代码
/// 模型列表槽函数处理过程
void GameDesigner::slotItemChanged(QListWidgetItem* item)
{
    QString file = item->data(Qt::UserRole + 1).toString();
    QtVariantProperty* prop = _pModelAttrMgr->addProperty(QVariant::String, "FileName");
    prop->setValue(file);

    ui._propertyTree->clear();

    ui._propertyTree->addProperty(prop);
}

/// 场景树模型槽处理函数 
void GameDesigner::slotitemClicked(QTreeWidgetItem* item, int)
{
    CELL::CELLNode* pNode = (CELL::CELLNode*)item->data(0, Qt::UserRole).toLongLong();
    if (pNode == NULL)
    {
        ui._propertyTree->clear();
        return;
    }
    else
    {
        ui._renderWidget->resetSelect();
        pNode->setFlag(CELL::CELLNode::FLAG_SELECT);

        QtVariantProperty* trans = _pModelAttrMgr->addProperty(QVariant::String, "Pos");
        QtVariantProperty* transX = _pModelAttrMgr->addProperty(QVariant::Double, "x");
        QtVariantProperty* transY = _pModelAttrMgr->addProperty(QVariant::Double, "y");
        QtVariantProperty* transZ = _pModelAttrMgr->addProperty(QVariant::Double, "z");
        trans->addSubProperty(transX);
        trans->addSubProperty(transY);
        trans->addSubProperty(transZ);
        transX->setValue(pNode->getTrans().x);
        transY->setValue(pNode->getTrans().y);
        transZ->setValue(pNode->getTrans().z);

        transX->setUserData((void*)pNode);
        transY->setUserData((void*)pNode);
        transZ->setUserData((void*)pNode);

        transX->setUserData1((void*)AttributeNodeType::ANT_POS_X);
        transY->setUserData1((void*)AttributeNodeType::ANT_POS_Y);
        transZ->setUserData1((void*)AttributeNodeType::ANT_POS_Z);

        QtVariantProperty* scale = _pModelAttrMgr->addProperty(QVariant::String, "Scale");
        QtVariantProperty* scaleX = _pModelAttrMgr->addProperty(QVariant::Double, "x");
        QtVariantProperty* scaleY = _pModelAttrMgr->addProperty(QVariant::Double, "y");
        QtVariantProperty* scaleZ = _pModelAttrMgr->addProperty(QVariant::Double, "z");
        scale->addSubProperty(scaleX);
        scale->addSubProperty(scaleY);
        scale->addSubProperty(scaleZ);
        scaleX->setValue(pNode->getScale().x);
        scaleY->setValue(pNode->getScale().y);
        scaleZ->setValue(pNode->getScale().z);

        scaleX->setUserData((void*)pNode);
        scaleY->setUserData((void*)pNode);
        scaleZ->setUserData((void*)pNode);

        scaleX->setUserData1((void*)AttributeNodeType::ANT_SCALE_X);
        scaleY->setUserData1((void*)AttributeNodeType::ANT_SCALE_Y);
        scaleZ->setUserData1((void*)AttributeNodeType::ANT_SCALE_Z);

        ui._propertyTree->clear();

        ui._propertyTree->addProperty(trans);
        ui._propertyTree->addProperty(scale);
    }
}

void GameDesigner::mySlotValueChanged(QtProperty* prop, const QVariant& val)
{
    QtVariantProperty* propertys = dynamic_cast<QtVariantProperty*>(prop);
    CELL::CELLNode* pNode = (CELL::CELLNode*)propertys->getUserData();
    if (propertys == NULL || pNode == NULL)
    {
        return;
    }
    CELL::float3    trans = pNode->getTrans();
    CELL::float3    scale = pNode->getScale();

    switch ((int)propertys->getUserData1())
    {
    case AttributeNodeType::ANT_POS_X:
    {
        trans.x = val.toReal();
        break;
    }
    case AttributeNodeType::ANT_POS_Y:
    {
        trans.y = val.toReal();
        break;
    }
    case AttributeNodeType::ANT_POS_Z:
    {
        trans.z = val.toReal();
        break;
    }
    case AttributeNodeType::ANT_SCALE_X:
    {
        scale.x = val.toReal();
        break;
    }
    case AttributeNodeType::ANT_SCALE_Y:
    {
        scale.y = val.toReal();
        break;
    }
    case AttributeNodeType::ANT_SCALE_Z:
    {
        scale.z = val.toReal();
        break;
    }
    default:
        break;
    }
    pNode->setTranslation(trans);
    pNode->setScale(scale);
}

主要是这些函数,里面具体的逻辑可以看我分享的代码。
代码链接

相关推荐
熬夜的猪仔5 小时前
QT多线程
qt
Octopus20775 小时前
【Godot】实现对话系统
游戏引擎·godot·游戏程序
꧁坚持很酷꧂8 小时前
QT登录系统界面
开发语言·qt
达斯维达的大眼睛17 小时前
qt小项目,简单的音乐播放器
开发语言·qt
君莫愁。18 小时前
【Unity】搭建基于字典(Dictionary)和泛型列表(List)的音频系统
数据结构·unity·c#·游戏引擎·音频
Ryan_Gosling20 小时前
QT-异步编程
开发语言·qt
虾球xz1 天前
游戏引擎学习第143天
学习·游戏引擎
信必诺1 天前
GStreamer —— 2.2、Windows下Qt加载GStreamer库后运行 - “教程2:GStreamer 概念“(附:完整源码)
qt·gstreamer
红黑色的圣西罗1 天前
Unity UGUI下优化需要射线检测类的UI元素的一种方式
unity·游戏引擎