14 Win32Window
官方代码($sdk)\examples\14.Win32Window
这个例子演示了如何在win32窗口里运行irr。用过windows api或MFC的都应该知道窗口句柄这东西,通过它,就能让irr在自己指定的窗口里运行,而不是irr自己创建的窗口里运行。开发经验丰富人一看到这个官方例子,应该就知道如何让irr运行到Qt和.Net的窗口里了。这个例子是用windows api写的,看懂执行流程要比用框架的简单的多,但对windows的消息驱动的工作流程不清楚的,看这例子就有点困难了,需要先去了解下如何用windows api写一个HelloWorld。
例子里一开头定义了两个全局的句柄,一个按钮的,另一个窗口的。
//按钮句柄
HWND hOKButton;
//窗口句柄
HWND hWnd;
接下来是消息处理的回调函数。窗口收到属于该窗口的消息时,会自动调用它的回调函数对消息进行处理。
CustomWndProc函数的样式是固定的,不能随意增减或改变参数,它将以函数指针的形式被窗口调用。参数HWND窗口句柄;UINT被传过来的消息ID,通过这ID可以知道具体是什么类型的消息;WPARAM和LPARAM附加到消息上的数据,它们可以是直接的数据,也可以是指针,具体需根据消息类型来确定,和API里面的MSG结构一样。
static LRESULT CALLBACK CustomWndProc(HWND hWnd, UINT message,WPARAM wParam, LPARAM lParam)
{
switch (message)
{
//处理窗口的命令
case WM_COMMAND:
{
HWND hwndCtl = (HWND)lParam;
int code = HIWORD(wParam);
//按钮发出的消息
if (hwndCtl == hOKButton)
{
//销毁窗口
DestroyWindow(hWnd);
//发送一个结束消息,告诉系统线程要结束了
PostQuitMessage(0);
return 0;
}
}
break;
//窗口销毁
case WM_DESTROY:
//发送一个结束消息,告诉系统线程要结束了
PostQuitMessage(0);
return 0;
}
//使用系统默认的消息处理函数
return DefWindowProc(hWnd, message, wParam, lParam);
}
这个例子里的消息处理函数功能很简单,它只处理了窗口收到的按钮点击消息和退出消息。如果需要处理其他的消息,增加相应的case就可以了。下面是主函数了,这次例子里主函数的代码跟前面的例子比,可以说完全不一样了。
int main()
{
//这里跟以往的例子一样,命令行上让用户选择irr驱动方式
video::E_DRIVER_TYPE driverType=driverChoiceConsole();
if (driverType==video::EDT_COUNT)
return 1;
//选择渲染窗口,这里有三种方式让用户选择。a、带有按钮的窗口,使用创建参数;b、带有按钮的窗口,使用beginScene;c、irr自有窗口,irr默认使用方式
printf("Select the render window (some dead window may exist too):\n"\
" (a) Window with button (via CreationParam)\n"\
" (b) Window with button (via beginScene)\n"\
" (c) Own Irrlicht window (default behavior)\n"\
" (otherKey) exit\n\n");
char key;
std::cin >> key;
if (key != 'a' && key != 'b' && key != 'c')
return 1;
//应用程序实例句柄
HINSTANCE hInstance = 0;
//注册窗口类
//irr注册类名
const char* Win32ClassName = "CIrrlichtWindowsTestDialog";
//填写窗口类的数据
WNDCLASSEX wcex;
//窗口类的结构体的大小
wcex.cbSize = sizeof(WNDCLASSEX);
//定义窗口风格
wcex.style = CS_HREDRAW | CS_VREDRAW;
//窗口使用的消息处理回调函数,就是代码一开头写的那个函数
wcex.lpfnWndProc = (WNDPROC)CustomWndProc;
//给类而外预留的空间默认设为0。我还没用到过,暂不明白有何用途。
wcex.cbClsExtra = 0;
//跟上面那个一样
wcex.cbWndExtra = DLGWINDOWEXTRA;
//该窗口的应用程序实例句柄
wcex.hInstance = hInstance;
//该窗口的图标
wcex.hIcon = NULL;
//该窗口的光标形式
wcex.hCursor = LoadCursor(NULL, IDC_ARROW);
//该窗口的背景画刷
wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW);
//该窗口使用的菜单
wcex.lpszMenuName = 0;
//该类的名字
wcex.lpszClassName = Win32ClassName;
//该窗口任务栏图标
wcex.hIconSm = 0;
//注册窗口类
RegisterClassEx(&wcex);
//创建窗口使用的窗口样式
DWORD style = WS_SYSMENU | WS_BORDER | WS_CAPTION | WS_CLIPCHILDREN | WS_CLIPSIBLINGS | WS_MAXIMIZEBOX | WS_MINIMIZEBOX | WS_SIZEBOX;
//窗口宽度
int windowWidth = 440;
//窗口高度
int windowHeight = 380;
//创建窗口
hWnd = CreateWindow( Win32ClassName, "Irrlicht Win32 window example",style, 100, 100, windowWidth, windowHeight,NULL, NULL, hInstance, NULL);
//获取窗口客户区矩形
RECT clientRect;
GetClientRect(hWnd, &clientRect);
windowWidth = clientRect.right;
windowHeight = clientRect.bottom;
//创建OK按钮
hOKButton = CreateWindow("BUTTON", "OK - Close", WS_CHILD | WS_VISIBLE | BS_TEXT,windowWidth - 160, windowHeight - 40, 150, 30, hWnd, NULL, hInstance, NULL);
// 在窗口上创建静态文本
CreateWindow("STATIC", "This is Irrlicht running inside a standard Win32 window.\n"\
"Also mixing with MFC and .NET Windows.Forms is possible.",
WS_CHILD | WS_VISIBLE, 20, 20, 400, 40, hWnd, NULL, hInstance, NULL);
//创建运行irr的窗口
HWND hIrrlichtWindow = CreateWindow("BUTTON", "",WS_CHILD | WS_VISIBLE | BS_OWNERDRAW,50, 80, 320, 220, hWnd, NULL, hInstance, NULL);
SExposedVideoData结构的定义见SExposedVideoData.h,它是用来保存一个描述操作系统和驱动的数据结构。前面的窗口选项按钮就在这里起作用,如果irr使用的是OpenGL驱动,后面还需要对它设置。
video::SExposedVideoData videodata((key=='b')?hIrrlichtWindow:0);
//现在已经有了一些窗口,使用Irrlicht的createDeviceEx()函数,就可以将irr应用到窗口中。它只需要一个窗口句柄,把它作为窗口ID参数,就可以跟平时一样使用irr引擎。
// 创建一个irr设备到按钮窗口中
SIrrlichtCreationParameters结构体,createDeviceEx()使用的创建参数结构。详见SIrrlichtCreationParameters.h文件
irr::SIrrlichtCreationParameters param;
//设置irr的驱动类型
param.DriverType = driverType;
//根据选择的窗口类型,将相应窗口句柄设为窗口ID参数
if (key=='a')
param.WindowId = reinterpret_cast<void*>(hIrrlichtWindow);
//创建irr设备
irr::IrrlichtDevice* device = irr::createDeviceEx(param);
if (!device)
return 1;
irr::scene::ISceneManager* smgr = device->getSceneManager();
video::IVideoDriver* driver = device->getVideoDriver();
//如果使用的是OpenGL驱动
if (driverType==video::EDT_OPENGL)
{
//获取窗口的DC
HDC HDc=GetDC(hIrrlichtWindow);
//设置OpenGL需要的像素格式
PIXELFORMATDESCRIPTOR pfd={0};
pfd.nSize=sizeof(PIXELFORMATDESCRIPTOR);
int pf = GetPixelFormat(HDc);
DescribePixelFormat(HDc, pf, sizeof(PIXELFORMATDESCRIPTOR), &pfd);
pfd.dwFlags |= PFD_DOUBLEBUFFER | PFD_SUPPORT_OPENGL | PFD_DRAW_TO_WINDOW;
pfd.cDepthBits=16;
pf = ChoosePixelFormat(HDc, &pfd);
SetPixelFormat(HDc, pf, &pfd);
//创建OpenGL的RC并将DC和RC加入到SExposedVideoData结构
videodata.OpenGLWin32.HDc = HDc;
videodata.OpenGLWin32.HRc=wglCreateContext(HDc);
//开启OpenGL的资源共享,这样就可以让两个RC使用相同的纹理等资源。还好有近两年自己一个人鼓捣OpenGL做引擎,看着部分没什么不懂的。
wglShareLists((HGLRC)driver->getExposedVideoData().OpenGLWin32.HRc, (HGLRC)videodata.OpenGLWin32.HRc);
}
//创建简单的3D场景,这里和以前的例子没什么不同
scene::ICameraSceneNode* cam = smgr->addCameraSceneNode();
cam->setTarget(core::vector3df(0,0,0));
scene::ISceneNodeAnimator* anim = smgr->createFlyCircleAnimator(core::vector3df(0,15,0), 30.0f);
cam->addAnimator(anim);
anim->drop();
scene::ISceneNode* cube = smgr->addCubeSceneNode(20);
cube->setMaterialTexture(0, driver->getTexture("../../media/wall.bmp"));
cube->setMaterialTexture(1, driver->getTexture("../../media/water.jpg"));
cube->setMaterialFlag( video::EMF_LIGHTING, false );
cube->setMaterialType( video::EMT_REFLECTION_2_LAYER );
smgr->addSkyBoxSceneNode(
driver->getTexture("../../media/irrlicht2_up.jpg"),
driver->getTexture("../../media/irrlicht2_dn.jpg"),
driver->getTexture("../../media/irrlicht2_lf.jpg"),
driver->getTexture("../../media/irrlicht2_rt.jpg"),
driver->getTexture("../../media/irrlicht2_ft.jpg"),
driver->getTexture("../../media/irrlicht2_bk.jpg"));
//又回到windows api程序了,显示窗口
ShowWindow(hWnd , SW_SHOW);
//刷新窗口
UpdateWindow(hWnd);
//处理消息队列
现在唯一缺少的就是前面所有的例子都出现过的绘图循环,那段一帧画面一循环的帧循环。可以简单的使用GetMessage,DispatchMessage等消息循环。也能调用Device->Run(),Irr引擎将在内部获取Windows的消息分发了。如果想使用自己的消息循环,就不需要调用Device->Run()了,但是rr就无法获取Windows分发的用户输入信息,那就只能使用DirectInput或其他方式了。
//通常使用的irr绘图循环
while (device->run())
{
driver->beginScene(true, true, 0, videodata);
smgr->drawAll();
driver->endScene();
}
//如果没有调用Device->run(),就必须这样自己获取消息了。注意,这两段while不能同时出现在代码里。
MSG msg;
while (true)
{
//获取窗口消息
if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
{
//转换加速键消息给窗口回调函数
TranslateMessage(&msg);
DispatchMessage(&msg);
//收到退出消息,停止循环
if (msg.message == WM_QUIT)
break;
}
//irr获取虚拟时间
device->getTimer()->tick();
//绘制图像,这里的beginScene也有些不同哦,它对应着开始时的窗口选项。
driver->beginScene(true, true, 0, (key=='c')?hIrrlichtWindow:0);
smgr->drawAll();
driver->endScene();
}
//程序结束,关闭和释放irr设备
device->closeDevice();
device->drop();
return 0;
}