毕业设计用到了OpenGL,由于不会用MFC和Win32API做窗口程序;自然选用了GLUT。GLUT很好用,就是每次写一堆Init,注册callback,觉得有点恶心,于是对他做了简单的OO封装。记录在此,如有同学有兴趣可以下载。
GLUT应用程序
直接使用GLUT的程序是这样的:
#include <GL/glut.h> #include <stdio.h> void display() { // OpenGL commands } // 一般按键(所有可打印字符,ESC也在内) void keyboardHander(unsigned char ch, int x, int y) { printf("key %d(%c) x: %d, y: %d ", ch, ch, x, y); fflush(stdout); } // 特殊按键 void specialKeyHandler(int key, int x, int y) { printf("special key"); switch(key) { case GLUT_KEY_UP: printf("%d(%s) ", key, "GLUT_KEY_UP"); break; case GLUT_KEY_DOWN: printf("%d(%s) ", key, "GLUT_KEY_DOWN"); break; case GLUT_KEY_LEFT: printf("%d(%s) ", key, "GLUT_KEY_LEFT"); break; case GLUT_KEY_RIGHT: printf("%d(%s) ", key, "GLUT_KEY_RIGHT"); break; default: printf("%d(%s) ", key, "Other Special keys"); } printf("x: %d, y: %d ", x, y); fflush(stdout); } // 鼠标按键 void mouseHandler(int button, int state, int x, int y) { printf("mouse pos: (%3d, %3d) button: %s(%d), state: %s(%d) ", x, y, GLUT_LEFT_BUTTON == button ? "GLUT_LEFT_BUTTON" : GLUT_RIGHT_BUTTON == button ? "GLUT_RIGHT_BUTTON" : GLUT_MIDDLE_BUTTON == button ? "GLUT_MIDDLE_BUTTON" : "UNKOW" , button, GLUT_UP == state ? "GLUT_UP" : GLUT_DOWN == state ? "GLUT_DOWN" : "UNKNOW" , state ); fflush(stdout); } // 鼠标拖动 void motionHandler(int x, int y) { printf("motion to %d, %d ", x, y); fflush(stdout); } // 鼠标移动 void passiveMotionHandler(int x, int y) { printf("passive motion to %d, %d ", x, y); fflush(stdout); } void testTimer(int i) { printf("Alarm %d ", i); fflush(stdout); if( i < 5 ) glutTimerFunc(1000, testTimer, i+1); } int main(int argc, char *argv[]) { glutInit(&argc, argv); glutInitDisplayMode(GLUT_SINGLE|GLUT_RGB); glutInitWindowSize(400, 300); glutInitWindowPosition(100, 100); glutCreateWindow("Getting started with OpenGL 4.3"); glutDisplayFunc(display); glutKeyboardFunc(keyboardHander); // 键盘按键(一般) glutSpecialFunc(specialKeyHandler); // 特殊按键 glutMouseFunc(mouseHandler); // 鼠标按键 glutMotionFunc(motionHandler); // 鼠标拖动 glutPassiveMotionFunc(passiveMotionHandler); // 鼠标移动 glutTimerFunc(1000, testTimer, 1); // 定时器 glutMainLoop(); // start main loop. return 0; }用起来还算简单,就是每次都写一堆Init和callback注册...
于是,我想到是否能够将它封装为一个基类,然后每次继承一下(有点像Java,C#的窗体程序)。
也就是每个成员函数对应一种事件的响应,比如onKey, onMouse等等。我们希望我们的程序像下面这样:
int main(int argc, char **argv) { GlutApp::initGlut(argc, argv); GlutApp* app = new DemoApp(); app->setTitle("a demo app"); app->setWindowsSize(800, 600); app->run(); delete app; return 0; }
Member function 如何作为Callback?
这里其实是两个问题。
第一个问题,member function的函数签名上有this指针,不能直接传给glut*Func作为callback,怎么办?
member function不行,很自然的想到static function;因为static function的函数签名上没有this指针。
第二个问题,static function如何能够调用member function,且与之关联的对象(this指针)能够在运行时期(或者用户程序)决定?
其一,static function调用member function自然要用到 static member。
其二,可让member function修改这个static member。
GlutApp
有了上面的分析,GlutApp类就可以轻易的写出来了:
GlutApp.h
#ifndef GLUT_APP_H #define GLUT_APP_H class GlutApp { public: typedef void (*MenuFuncPtr)(void); struct MenuEntry { int id; const char* str; MenuFuncPtr fun; }; // 当前 App 实例指针,指向子类实例 static GlutApp* s_pCurrentApp; // 右键菜单 项数最大值 static const int MAX_MENU = 32; // ctor GlutApp(); // getter and setters: static void initGlut(int argc, char** argv) { s_argc = argc; s_argv = argv; } void setDisplayMode(unsigned int mode) { m_displayMode = mode; } void setWindowsSize(int w, int h) { m_winWidth = w; m_winHeight = h; } int getWindowWidth() { return m_winWidth; } int getWindowHeight() { return m_winHeight; } void setWindowsPos(int x, int y) { m_winPosX = x; m_winPosY = y; } void setTitle(char *title) { m_title = title; } void run(); void addRightMenu(const char *str, MenuFuncPtr fun); // 初始化 virtual void onInit(){} ////////////////////////////////////////////////////////////////////////// // GLUT delegate callbacks: // 空闲函数 virtual void onIdle(){} // 图形显示(OpenGL绘图指令) virtual void onDisplay() = 0; // 子类必须重写;不能实例化该类 // 窗口大小改变 virtual void onResize(int w, int h){} ////////////////////////////////////////////////////////////////////////// // 键盘事件响应 方法: // 一般按键(可打印字符,ESC) virtual void onKey(unsigned char key, int x, int y){} // 一般按键 按下 virtual void onKeyDown(unsigned char key, int x, int y) {} // 特殊按键(除一般按键外按键) virtual void onSpecialKey(int key, int x, int y){} // 特殊按键按下 virtual void onSpecialKeyDown(int key, int x, int y){} ////////////////////////////////////////////////////////////////////////// // 鼠标事件响应 方法: // 鼠标按键 //! @param button: The button parameter is one of GLUT LEFT BUTTON, GLUT MIDDLE BUTTON, or GLUT RIGHT BUTTON. //! @param state: The state parameter is either GLUT UP or GLUT DOWN indicating // whether the callback was due to a release or press respectively. virtual void onMousePress(int button, int state, int x, int y){} // 鼠标移动 virtual void onMouseMove(int x, int y){} // 鼠标拖动 virtual void onMousePressMove(int x,int y){} ////////////////////////////////////////////////////////////////////////// // 定时器相关 方法: virtual void onTimer() {} void setTimer(int delay, int period = 0); protected: void registerMenus(); // actual GLUT callback functions: static void KeyboardCallback(unsigned char key, int x, int y); static void KeyboardUpCallback(unsigned char key, int x, int y); static void SpecialKeyboardCallback(int key, int x, int y); static void SpecialKeyboardUpCallback(int key, int x, int y); static void ReshapeCallback(int w, int h); static void IdleCallback(); static void MouseFuncCallback(int button, int state, int x, int y); static void MotionFuncCallback(int x,int y); static void MousePassiveCallback(int x, int y); static void DisplayCallback(); static void MenuCallback(int menuId); static void TimerCallback(int period); private: unsigned int m_displayMode; // for glutInit static int s_argc; static char** s_argv; char *m_title; // for glutSetWindowSize int m_winWidth; int m_winHeight; // for windows position int m_winPosX; int m_winPosY; // for menus: int m_menuCount; MenuEntry m_menuEntry[MAX_MENU]; // for timer: int m_delay; int m_period; }; #endif // GLUT_APP_H
#include <gl/glut.h> #include <assert.h> #include <stdio.h> #include "GlutApp.h" int GlutApp::s_argc = 0; char** GlutApp::s_argv = 0; GlutApp* GlutApp::s_pCurrentApp = 0; int g_iLastWindow = 0; void GlutApp::run() { GlutApp* lastApp = GlutApp::s_pCurrentApp; GlutApp::s_pCurrentApp = this; GlutApp* app = GlutApp::s_pCurrentApp; assert(app); int screenW = glutGet(GLUT_SCREEN_WIDTH); int screenH = glutGet(GLUT_SCREEN_HEIGHT); if (!app->m_winWidth) { app->m_winWidth = screenW / 2; app->m_winHeight = screenH / 2; } if (!app->m_winPosX) { app->m_winPosX = (screenW - app->m_winWidth) / 2; app->m_winPosY = (screenH - app->m_winHeight) / 2; } if (!lastApp) // first time calling Glut::run(). { // glutInit that should only be called exactly once in a GLUT program. glutInit(&this->s_argc, this->s_argv); glutInitDisplayMode(this->m_displayMode); glutInitWindowPosition(app->m_winPosX, app->m_winPosY); glutInitWindowSize(app->m_winWidth, app->m_winHeight); glutCreateWindow(app->m_title); g_iLastWindow = glutGetWindow(); // printf("create window: %d ", g_iLastWindow); } else { glutDestroyWindow(g_iLastWindow); glutInitDisplayMode(this->m_displayMode); glutInitWindowPosition(app->m_winPosX, app->m_winPosY); glutInitWindowSize(app->m_winWidth, app->m_winHeight); glutCreateWindow(app->m_title); g_iLastWindow = glutGetWindow(); // printf("create window: %d ", g_iLastWindow); } app->onInit(); // register keyboard callbacks glutKeyboardFunc(GlutApp::KeyboardCallback); glutKeyboardUpFunc(GlutApp::KeyboardUpCallback); glutSpecialFunc(GlutApp::SpecialKeyboardCallback); glutSpecialUpFunc(GlutApp::SpecialKeyboardUpCallback); // register mouse callbacks glutMouseFunc(GlutApp::MouseFuncCallback); glutMotionFunc(GlutApp::MotionFuncCallback); glutPassiveMotionFunc(GlutApp::MousePassiveCallback); // register menus: registerMenus(); // regitser windows resize callback glutReshapeFunc(GlutApp::ReshapeCallback); // register render callback glutDisplayFunc(GlutApp::DisplayCallback); // register timer callbacks: if (app->m_delay) { glutTimerFunc(app->m_delay, GlutApp::TimerCallback, app->m_period); } // register idle callback glutIdleFunc(GlutApp::IdleCallback); GlutApp::IdleCallback(); glutMainLoop(); } GlutApp::GlutApp() { m_displayMode = GLUT_DOUBLE | GLUT_RGBA | GLUT_DEPTH | GLUT_STENCIL; m_menuCount = 0; m_delay = 0; m_period = 0; m_winPosX = 0; m_winPosY = 0; m_winWidth = 0; m_winHeight = 0; } void GlutApp::KeyboardCallback( unsigned char key, int x, int y ) { GlutApp::s_pCurrentApp->onKey(key,x,y); } void GlutApp::KeyboardUpCallback( unsigned char key, int x, int y ) { GlutApp::s_pCurrentApp->onKeyDown(key,x,y); } void GlutApp::SpecialKeyboardCallback( int key, int x, int y ) { GlutApp::s_pCurrentApp->onSpecialKey(key,x,y); } void GlutApp::SpecialKeyboardUpCallback( int key, int x, int y ) { GlutApp::s_pCurrentApp->onSpecialKeyDown(key,x,y); } void GlutApp::ReshapeCallback( int w, int h ) { GlutApp::s_pCurrentApp->setWindowsSize(w, h); GlutApp::s_pCurrentApp->onResize(w,h); } void GlutApp::IdleCallback() { GlutApp::s_pCurrentApp->onIdle(); } void GlutApp::MouseFuncCallback( int button, int state, int x, int y ) { GlutApp::s_pCurrentApp->onMousePress(button,state,x,y); } void GlutApp::MotionFuncCallback( int x,int y ) { GlutApp::s_pCurrentApp->onMousePressMove(x,y); } void GlutApp::MousePassiveCallback( int x, int y ) { GlutApp::s_pCurrentApp->onMouseMove(x, y); } void GlutApp::DisplayCallback( void ) { GlutApp::s_pCurrentApp->onDisplay(); } void GlutApp::addRightMenu( const char *str, MenuFuncPtr fun ) { m_menuEntry[m_menuCount].id = m_menuCount; m_menuEntry[m_menuCount].str = str; m_menuEntry[m_menuCount].fun = fun; m_menuCount++; } void GlutApp::registerMenus() { if (m_menuCount > 0) { glutCreateMenu(GlutApp::MenuCallback); for (int i=0; i<m_menuCount; ++i) { glutAddMenuEntry(m_menuEntry[i].str, m_menuEntry[i].id); } glutAttachMenu(GLUT_RIGHT_BUTTON); } } void GlutApp::MenuCallback( int menuId ) { for (int i=0; i<GlutApp::s_pCurrentApp->m_menuCount; ++i) { if (menuId == GlutApp::s_pCurrentApp->m_menuEntry[i].id) { GlutApp::s_pCurrentApp->m_menuEntry[i].fun(); } } } void GlutApp::setTimer( int delay, int period ) { this->m_delay = delay; this->m_period = period; } void GlutApp::TimerCallback( int period ) { // printf("Timer Alarm! "); GlutApp::s_pCurrentApp->onTimer(); if (period) { glutTimerFunc(period, GlutApp::TimerCallback, period); } }
一个Demo
“伟大的三角形”,如同Hello world在编程语言教程里一样有名:
#include <windows.h> // 这里使用的是 Windows SDK 实现的OpenGL,必须写在<gl/gl.h>之前 #include <gl/gl.h> #include <gl/glut.h> #include <stdio.h> #include "GlutApp.h" class TestApp : public GlutApp { virtual void onSpecialKeyDown( int key, int x, int y ) { printf("onKeyDown: %d(%c), <%d, %d> ", key, key, x, y); } virtual void onSpecialKey( int key, int x, int y ) { printf("onSpecialKey: %d(%c), <%d, %d> ", key, key, x, y); } virtual void onKeyDown( unsigned char key, int x, int y ) { printf("onKeyDown: %d(%c), <%d, %d> ", key, key, x, y); } virtual void onKey( unsigned char key, int x, int y ) { printf("onKey: %d(%c), <%d, %d> ", key, key, x, y); } virtual void onMouseMove( int x, int y ) { printf("onMouseMove: %d, %d ", x, y); } virtual void onMousePress( int button, int state, int x, int y ) { printf("onMousePress: %d, %d, %d, %d ", button, state, x, y); } virtual void onMousePressMove( int x,int y ) { printf("onMousePressMove: %d, %d ", x, y); } virtual void onInit() { printf("OnInit "); glClearColor(0.0, 0.0, 0.0, 0.0); glShadeModel(GL_FLAT); } virtual void onDisplay() { glClear(GL_COLOR_BUFFER_BIT); // glPolygonMode(GL_FRONT, GL_LINE); glBegin(GL_TRIANGLES); glColor3f(1, 0, 0); glVertex2f(-1, -1); glVertex2f(1, -1); glVertex2f(0, 1); glEnd(); glFlush(); glutSwapBuffers(); } virtual void onResize( int w, int h ) { printf("resize window: %d, %d ", w, h); } virtual void onIdle() { } }; void menu1() { printf("menu1 selected! "); } void menu2() { printf("menu2 selected! "); } void fullScreen() { glutFullScreen(); } void exitApp() { exit(0); } int main(int argc, char **argv) { TestApp test; test.initGlut(argc, argv); test.setTitle("AppTest"); test.setWindowsSize(640, 480); test.setDisplayMode(GLUT_RGBA | GLUT_SINGLE); test.addRightMenu("menu1", menu1); test.addRightMenu("menu2", menu2); test.addRightMenu("full screen", fullScreen); test.addRightMenu("exit", exitApp); test.run(); return 0; }