我终于把界面换成qt的了,即使我从大二开始就用mfc,一直用到去年上半年。也终于到了,我实在受不了mfc代码混乱的时候了,qt那么方便的东西,我为什么不早点用了。这里就不吐槽mfc的混乱,qt的方便了。在mfc里面建立一个复杂的界面,比如说有dock窗口,或者tab页面的,代码多,而且混乱麻烦,更无语的是,每次我都必须去搜索下,解决各种各样的问题,有的还可以理解,有的就是莫名其妙了。更别说什么代码清晰,跨平台之类的,何止是天方夜谭。
下面,说说我自己逐渐搭建的这个框架吧。其实,这个框架是从我上一个项目里,使用mfc的单文档单视图,主窗口里面有几个dockpane,视图里面放置了渲染OpenGL的子窗口,过渡来的。在qt的框架里面,mfc所有的这些恶心的东西都没有了,只剩下一个MainWindow类。在这个类里面,创建菜单,工具条,状态条,主窗口,dock窗口,tab窗口。代码简洁方便。渲染opengl的窗口设置为MainWindow的主widget。渲染窗口只是一个普通的widget而已,可以任意放置。
这样的做法能够把界面和渲染窗口独立出来,渲染窗口对应一个独立的类(COglWidget),所以方便创建不同类型的复杂界面。我觉得我应该先用viso画个类图,才能说清楚。下图是我的界面。中间是COglWidget。右侧是一个DockWidget,里面放置了个设置Mesh属性的widget。
通过主窗口的构造函数,可以看看如何创建这个界面,虽然在主窗口里面做这么的事情不是个好的习惯。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| CMainWindow::CMainWindow(QWidget *parent) : QMainWindow(parent) { setWindowTitle(tr("Skeleton Extract"));
m_pSEMesh = new CSEMesh(); m_pOglForSE = new COglWidget(this, m_pSEMesh); m_pSEMesh->SetOglWidget(m_pOglForSE); setCentralWidget((QWidget*)m_pOglForSE);
m_pPCMesh = new CPointCloudMesh(); m_pOglForPC = new COglWidget(this, m_pPCMesh); m_pOglForPC->setFixedSize(960, 720); m_pOglForPC->setWindowTitle(tr("Point Cloud For Geodesic Matrix Eigen Vector")); m_pOglForPC->move((QApplication::desktop()->width() - m_pOglForPC->width()) / 2, (QApplication::desktop()->height() - m_pOglForPC->height())/2); m_pOglForPC->hide(); m_pOglForPC->setWindowFlags(Qt::Window);
CreateActions(); CreateMenus(); CreateToolBars(); CreateStatusBar(); CreateDockWidgets();
setMinimumSize(960, 720); setWindowState(Qt::WindowMaximized);
std::string strName = "MeshData\\bunny_3k.m"; m_pSEMesh->LoadModel(strName.c_str()); m_pMeshNameLabel->setText(QString(strName.c_str())); }
|
再来看一个使用tabwidget的界面,记得在mfc中使用tab页面应该是比较麻烦,我也没用过。不过,在qt里面,唯一需要改变的是把tabwidget设置为central的widget,把其它widget加入到tab页面里面就行了。效果如图:
这个界面的创建代码如下,这里我把菜单,工具条,状态栏都去掉了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
| CMainWindow::CMainWindow(QWidget *parent) : QMainWindow(parent) { setWindowTitle(tr("Virtual Box"));
m_pMesh = new CCubeMesh(); m_pOglWidget = new COglWidget(this, m_pMesh);
m_pMainViewMesh = new CCubeMesh(); m_pMainViewMesh->DrawStyle(CCubeMesh::MAIN_VIEW); m_pMainViewWidget = new CViewWidget(this, m_pMainViewMesh);
m_pTopViewMesh = new CCubeMesh(); m_pTopViewMesh->DrawStyle(CCubeMesh::TOP_VIEW); m_pTopViewWidget = new CViewWidget(this, m_pTopViewMesh);
m_pSideViewMesh = new CCubeMesh(); m_pSideViewMesh->DrawStyle(CCubeMesh::SIDE_VIEW); m_pSideViewWidget = new CViewWidget(this, m_pSideViewMesh);
setMinimumSize(960, 720); setWindowState(Qt::WindowMaximized);
m_pTabWidget = new QTabWidget(this); m_pTabWidget->addTab(m_pOglWidget, "Cube View"); m_pTabWidget->addTab(m_pMainViewWidget, "Main View"); m_pTabWidget->addTab(m_pTopViewWidget, "Top View"); m_pTabWidget->addTab(m_pSideViewWidget, "Side View"); setCentralWidget((QWidget*)m_pTabWidget); }
|
从上面的代码中,可以看到创建一个OglWidget需要一个Mesh指针。在我的框架中,COglWidget内中只使用几个固定的Mesh函数。因此,可以把COglWidget实现为依赖于一个IMesh接口,这样就进一步把OpenGL窗口和Mesh分离开来。不管Mesh类的具体实现如何,OpenGL窗口只需要依赖IMesh接口,因此,第一个界面里面的Mesh类实际上组合了一个CGAL的CGAL::Polyhedron_3对象。而第二个界面的Mesh类是一个简单的渲染长方体的类,不过支持渲染多个视图而已。
下面说说,COglWidget类具有哪些功能。首先,渲染Mesh,包括设置视口,eye,投影,光照,背景等等,其次,处理鼠标按键操作模型,我的实现是把所有的鼠标按键消息都丢给了下层的一个trackball类,这个trackball类支持旋转缩放移动模型,另外还写了几个打开文件,以及选取背景色的操作。
现在到了Mesh的部分了。因为跟OpenGL窗口相关的只是IMesh接口,所以,我可以任意实现实际的Mesh类。
IMesh的定义如下,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58
| #ifndef IMESH_H #define IMESH_H #include "libyx/vertex.h"
namespace LibYX { struct IMesh { IMesh() { m_pMatMV = new double[16]; m_pMatProj = new double[16]; m_pViewport = new int[4]; }
virtual ~IMesh() { FreeModel(); delete[] m_pMatMV; delete[] m_pMatProj; delete[] m_pViewport; }
virtual bool LoadModel(const char* pFilename) { return true; } virtual void SaveModel(const char* sFilename) {} virtual void UnifyModel() {}
virtual bool HasModelLoad() const { return true; } virtual void FreeModel() {}
virtual void InitData() {} virtual void DestroyData() {}
virtual void DrawScene() { glGetDoublev(GL_MODELVIEW_MATRIX, m_pMatMV); glGetDoublev(GL_PROJECTION_MATRIX, m_pMatProj); glGetIntegerv(GL_VIEWPORT, m_pViewport); }
void WindowWidth(int nWidth) { m_nWinWidth = nWidth; } int WindowWidth() const { return m_nWinWidth; } void WindowHeight(int nHeight) { m_nWinHeight = nHeight; } int WindowHeight() const { return m_nWinHeight; }
protected: double* m_pMatMV; double* m_pMatProj; int* m_pViewport; int m_nWinWidth; int m_nWinHeight;
protected: v3d m_vMax, m_vMin; }; };
#endif
|
其中,DrawScene()是渲染接口,另外还有加载模型的接口等,里面还有些数据。我用CTriMesh继承IMesh接口,同时组合Enriched_Model *m_pModel的指针,创建一个CGAL的多边形网格模型,实际的操作基本都交给m_pModel。这个类的定义太复杂了,就不贴了。这个类里面我集成了基本操作,比如按照点,线,面方式渲染,渲染包围盒,坐标轴,法线,顶点,边,面的索引等,加载和保存模型等等。因此,CTriMesh作为模型的基类,在实际的项目中,再继承CTriMesh,比如第一个界面中的CSEMesh,在CSEMesh中进行具体的处理。
所以,实际的算法实现都在CSEMesh里面。因为,IMesh和CTriMesh,COglWidget可以固化了。能够变化的只是CMainWindow。如果要添加新的代码,只能继承CTriMesh,在子类里面实现具体的算法,也算是对修改关闭对扩展开放吧。
因此,如果再做类似的东西,我就有一个很方便的框架了。。。鉴于不少人留言要代码,有兴趣的可以参考这个QtRenderMesh。