Irrlicht学习备忘录——9 Meshviewer

发布时间:2014-10-23 23:28:14
来源:分享查询网

9 Meshviewer     官方代码($sdk)\examples\09.Meshviewer     My God!一千多行代码,新手看到一定觉着头晕眼花。我这个搞通信的外行,第一次看到这个例子时,也觉着害怕,这么多代码,真怀疑会看不懂。不过还好,irr的代码风格非常易读,有前面例子的基础,还是很容易读懂的。这个例子可以算前面例子的综合复习,它使用irr的场景管理和用户界面GUI创建了一个Mesh查看器。例子中使用的GUI控件,比05.UserInterface中使用的种类更多,按钮,窗口,工具栏,菜单,下拉列表,编辑框,消息窗口统统都用到。场景中使用了天空盒场景节点,还做了摄像机切换,同时也使用了irr的XML解析器。内容真实丰富啊。接下来就一个功能一个功能的看懂这个例子。     切换两种不同的摄象机。切换摄像机其实很简单,场景管理器里已经提供了现成的方法setActiveCamera(摄像机),使用此方法就行了,不过需要注意一点就是,FPS和Maya摄像机已经提供了用户控制摄像机位置和视角的功能,在切换前应该设置摄像机是否接收输入。例子中将这功能写出了setActiveCamera方法,方便以后调用。     //设置活动摄像机,参数为摄像机场景节点     void setActiveCamera(scene::ICameraSceneNode* newActive)     {         // Device是全局变量,使用的irr设备         if (0 == Device)             return;         //通过场景管理器获取当前的活动摄像机         scene::ICameraSceneNode * active = Device->getSceneManager()->getActiveCamera();         //将当前的活动摄像机接收输入功能关闭         active->setInputReceiverEnabled(false);         //将替换摄像机接收输入功能开启         newActive->setInputReceiverEnabled(true);         //使用场景管理器将替换摄像机设为当前活动摄像机         Device->getSceneManager()->setActiveCamera(newActive);     }     setSkinTransparency设置GUI皮肤透明度,这个基本没什么好说的,例子05.UserInterface中已经出现过,只是这次把它写成了一个函数,成了一个功能模块,以后好调用。第一个参数是透明度,第二个是要修改的GUI皮肤。     void setSkinTransparency(s32 alpha, irr::gui::IGUISkin * skin)     {         for (s32 i=0; i<irr::gui::EGDC_COUNT ; ++i)         {             video::SColor col = skin->getColor((EGUI_DEFAULT_COLOR)i);             col.setAlpha(alpha);             skin->setColor((EGUI_DEFAULT_COLOR)i, col);         }     }     updateScaleInfo更新模型的缩放比例信息。也就是将模型的显示比例,以直观的数字显示到GUI上。传入场景节点做参数。以前例子里没用到的功能有,场景节点的getScale,这个看名称也就知道,获取缩放比例,没什么好说的;还有个就是GUI元素里的getElementFromId,这个是获取GUI元素下指定ID的子元素,这个功能在文件存储和载入GUI界面时很有用。     void updateScaleInfo(scene::ISceneNode* model)     {         //获取工具箱窗口         IGUIElement* toolboxWnd = Device->getGUIEnvironment()->getRootGUIElement()->getElementFromId(GUI_ID_DIALOG_ROOT_WINDOW, true);         //如果没有工具箱窗口,信息也就不用显示了,直接退出         if (!toolboxWnd)             return;         if (!model)//场景节点不存在         {             //设置编辑框内XYZ三个轴方向缩放比例的显示文字为“-”             toolboxWnd->getElementFromId(GUI_ID_X_SCALE, true)->setText( L"-" );             toolboxWnd->getElementFromId(GUI_ID_Y_SCALE, true)->setText( L"-" );             toolboxWnd->getElementFromId(GUI_ID_Z_SCALE, true)->setText( L"-" );         }         else//场景节点存在         {             //获取场景节点的缩放比例             core::vector3df scale = model->getScale();             //设置编辑框内XYZ三个轴方向缩放比例             toolboxWnd->getElementFromId(GUI_ID_X_SCALE, true)->setText( core::stringw(scale.X).c_str() );             toolboxWnd->getElementFromId(GUI_ID_Y_SCALE, true)->setText( core::stringw(scale.Y).c_str() );             toolboxWnd->getElementFromId(GUI_ID_Z_SCALE, true)->setText( core::stringw(scale.Z).c_str() );         }     }     showAboutText这个函数没什么好说的,就是添加一个消息对话框。      loadModel这个的代码也太长了,其实还可以细分成几个函数,那样看起来就容易多了。 它用来从文件载入一个模型,传入参数为文件名。这里面又有irr的文件系统操作了,值得看看。     void loadModel(const c8* fn) {     //创建个文件路径和文件扩展名     io::path filename(fn);     io::path extension;     //获取文件扩展名     core::getFileNameExtension(extension, filename);     //将扩展名转为小写字母     extension.make_lower();     // 如果文件名是支持的纹理格式     if (extension == ".jpg" || extension == ".pcx" ||         extension == ".png" || extension == ".ppm" ||         extension == ".pgm" || extension == ".pbm" ||         extension == ".psd" || extension == ".tga" ||         extension == ".bmp" || extension == ".wal" ||         extension == ".rgb" || extension == ".rgba")     {         //创建纹理,这个前面的例子里出现很多次了         video::ITexture * texture =             Device->getVideoDriver()->getTexture( filename );         if ( texture && Model )         {             // 重新加载纹理             Device->getVideoDriver()->removeTexture(texture);             texture = Device->getVideoDriver()->getTexture( filename );             //设置模型的纹理             Model->setMaterialTexture(0, texture);         }         //纹理文件处理结束,退出         return;     }     //如果是压缩文件,就把它放置到文件管理器系统中进行处理     else if (extension == ".pk3" || extension == ".zip" || extension == ".pak" || extension == ".npk")     {         Device->getFileSystem()->addFileArchive(filename.c_str());         //压缩文件处理结束,退出         return;     }     //将模型载入到引擎     //如果已经存在模型,就移除它     if (Model)         Model->remove();     Model = 0;          //如果是.irr场景文件     if (extension==".irr")     {         //创建一个场景节点列表         core::array<scene::ISceneNode*> outNodes;         //通过场景管理器载入场景文件         Device->getSceneManager()->loadScene(filename);         //筛选出AnimatedMeshSceneNode节点         Device->getSceneManager()->getSceneNodesFromType(scene::ESNT_ANIMATED_MESH, outNodes);         //将第一个符合条件的场景节点做为模型         if (outNodes.size())             Model = outNodes[0];         //irr场景文件处理结束,退出         return;     }     //载入一个AnimatedMesh模型     scene::IAnimatedMesh* m = Device->getSceneManager()->getMesh( filename.c_str() );     if (!m)     {         // 如果模型 Load 失败,则弹出警告窗口         if (StartUpModelFile != filename)             Device->getGUIEnvironment()->addMessageBox(             Caption.c_str(), L"The model could not be loaded. " \             L"Maybe it is not a supported file format.");         return;     }     //如果允许使用八叉树,就已八叉树场景节点载入     if (Octree)         Model = Device->getSceneManager()->addOctreeSceneNode(m->getMesh(0));     else     {         //载入动画节点,并将动画速度设为30         scene::IAnimatedMeshSceneNode* animModel = Device->getSceneManager()->addAnimatedMeshSceneNode(m);         animModel->setAnimationSpeed(30);         Model = animModel;     }     // 设置默认材质属性     Model->setMaterialFlag(video::EMF_LIGHTING, UseLight);     Model->setMaterialFlag(video::EMF_NORMALIZE_NORMALS, UseLight); //    Model->setMaterialFlag(video::EMF_BACK_FACE_CULLING, false);     Model->setDebugDataVisible(scene::EDS_OFF);     // 取消菜单中选项     gui::IGUIContextMenu* menu = (gui::IGUIContextMenu*)Device->getGUIEnvironment()->getRootGUIElement()->getElementFromId(GUI_ID_TOGGLE_DEBUG_INFO, true);     if (menu)         for(int item = 1; item < 6; ++item)             menu->setItemChecked(item, false);     //更新缩放信息     Updatescaleinfo(Model); }     createToolBox创建工具箱窗口,窗口上主要就是有一个标签页,在标签页上有三个显示模型缩放比例的编辑框,还有两个滑动条用来控制GUI透明度,和模型动画速度。里面用到的GUI元素,前面的例子基本都用过,首次出现的只有标签元素。要使用标签页,首先需要在窗口上添加一个IGUITabControl元素,它用来控制标签页的切换;然后就是在IGUITabControl上添加IGUITab元素。IGUITab就是真正的标签页面,需要在页面上怎么什么内容,就是直接在把新添加的GUI元素的父节点设为IGUITab就可以了。具体关键代码如下     // 创建工具箱窗口     IGUIWindow* wnd = env->addWindow(core::rect<s32>(600,45,800,480),false, L"Toolset", 0, GUI_ID_DIALOG_ROOT_WINDOW);     //创建标签控制元素     IGUITabControl* tab = env->addTabControl(core::rect<s32>(2,20,800-602,480-7), wnd, true, true);     //创建一个标签页面     IGUITab* t1 = tab->addTab(L"Config");     //添加一个静态文本框到标签页面     env->addStaticText(L"Scale:",core::rect<s32>(10,20,60,45), false, false, t1);     //添加一个文本编辑框到标签页面     env->addEditBox(L"1.0", core::rect<s32>(40,46,130,66), true, t1, GUI_ID_X_SCALE);     //添加一个按钮到标签页面     env->addButton(core::rect<s32>(10,134,85,165), t1, GUI_ID_BUTTON_SET_SCALE,L"Set");     //添加一个滑动条     IGUIScrollBar* scrollbar = env->addScrollBar(true,core::rect<s32>(10,225,150,240), t1,GUI_ID_SKIN_TRANSPARENCY);     updateToolBox更新工具箱信息。这个函数的功能就只是更新模型动画的帧速。里面首次使用的有IAnimatedMeshSceneNode的getAnimationSpeed方法和getFrameNr方法。getAnimationSpeed返回的动画模型节点的动画帧速,getFrameNr方法返回的是当前节点的显示动画帧序号。     onKillFocus当户用移动摄像机时按下ALT+Tab,强制FPS摄像机继续移动。我也没搞明白这段代码有什么用。看代码里的注释,它就是发送irr的按键事件给摄像机的运动器,使摄像机继续运动。但代码里设置按键按下标志确是false,跟注释描述的功能似乎刚好相反。没搞懂就不研究它了。     hasModalDialog检查当前是否有一个modal对话框。使用IGUIEnvironment的getFocus可以获取当前焦点所在的GUI元素;使用IGUIElement的isVisible方法可以判断该元素是否显示,用hasType可以判断是否有相关类型的GUI元素,通过getParent可以得到该GUI元素的父元素。在hasModalDialog里,通过当前拥有焦点的GUI元素,不断逐级向父元素查询是否有EGUIET_MODAL_SCREEN类型的GUI元素,直到查到modal对话框或已到GUI跟元素。     MyEventReceiver类,这个在05.UserInterface里已经出现过了,用户自定义的事件接收器。里面就是根据相应的GUI事件做出相应的控制,没什么好说明的。     OnKeyUp这个算是自己设计热键,根据那个按键弹起,做出相应的事件处理。它在MyEventReceiver类中EET_KEY_INPUT_EVENT事件处理处被调用。     OnMenuItemSelected用来处理选择菜单项目事件,里面根据预设的GUI元素的ID来判断具体是哪个菜单项被选择中,让后做出相应的事件处理。它同样在MyEventReceiver类中 被调用,不同的是选择菜单项目事件是EGET_MENU_ITEM_SELECTED。     OnTextureFilterSelected纹理材质过滤选择。这个函数就是相应纹理过滤的下拉列表的事件。通过IGUIComboBox的getSelected可以知道具体是第几个下拉列表项被选中。设置不同的纹理材质过滤,其实用的就是场景节点的setMaterialFlag,打开或关闭相应的材质标志。IGUIComboBox触发的事件是EGET_COMBO_BOX_CHANGED。     最后要看的代码就是main函数了。这里面大部分代码都很熟悉,前面的例子中就出现过 。已经熟悉的东西就不再研究了,只看新鲜的东西。     smgr->getParameters()->setAttribute(scene::COLLADA_CREATE_SCENE_INSTANCES, true);//获取场景管理器的参数,设置COLLADA_CREATE_SCENE_INSTANCES属性。这个属性设置了有什么用,我真搞不明白,设不设似乎都没影响。     smgr->setAmbientLight(video::SColorf(0.3f,0.3f,0.3f));//设置环境光     Device->getFileSystem()->addFileArchive("../../media/");//添加一个文件存档     io::IXMLReader* xml = Device->getFileSystem()->createXMLReader( L"config.xml");//这个比较有用,它irr的xml解析器。从irr的文件系统可以创建xml解析器。IXMLReader的read方法,将IXMLReader移动到下一个xml节点;getNodeType 方法获取节点的类型;getNodeName 方法获取节点名字;getAttributeValue 获取节点的属性。     //循环读取下一个xml节点     while(xml && xml->read())     {         //获取节点类型         switch(xml->getNodeType())         {         case io::EXN_TEXT://文本节点             //获取文本节点内容             MessageText = xml->getNodeData();             break;         case io::EXN_ELEMENT://xml元素             {                 //如果是startUpModel元素,将元素中file属性的值赋值给StartUpModelFile                 if (core::stringw("startUpModel") == xml->getNodeName())                     StartUpModelFile = xml->getAttributeValue(L"file");                 //如果是messageText 元素,将元素中caption 属性的值赋值给Caption                 else if (core::stringw("messageText") == xml->getNodeName())                     Caption = xml->getAttributeValue(L"caption");             }             break;         default:             break;         }     }     前面GUI的例子中没有教使用菜单,在这例子里有了。     //通过GUI环境添加一个菜单     gui::IGUIContextMenu* menu = env->addMenu();     //给IGUIContextMenu 添加项目     menu->addItem(L"File", -1, true, true);//文件菜单     menu->addItem(L"View", -1, true, true);//查看菜单     menu->addItem(L"Camera", -1, true, true);//摄像机菜单     menu->addItem(L"Help", -1, true, true);//帮助菜单     //获取第一个菜单项目,在项目下添加子项目     gui::IGUIContextMenu* submenu;     submenu = menu->getSubMenu(0);     submenu->addItem(L"Open Model File & Texture...", GUI_ID_OPEN_MODEL);     submenu->addItem(L"Set Model Archive...", GUI_ID_SET_MODEL_ARCHIVE);     submenu->addItem(L"Load as Octree", GUI_ID_LOAD_AS_OCTREE);     submenu->addSeparator();     submenu->addItem(L"Quit", GUI_ID_QUIT);     前面的例子中同样没有使用GUI的工具栏和下拉列表。这里看看工具栏和下拉列表如何使用。     //添加一个工具栏     gui::IGUIToolBar* bar = env->addToolBar();     //创建一个工具按钮图标的纹理     video::ITexture* image = driver->getTexture("open.png");     //添加一个工具按钮     bar->addButton(GUI_ID_BUTTON_OPEN_MODEL, 0, L"Open a model",image, 0, false, true);     //添加一个下拉列表     gui::IGUIComboBox* box = env->addComboBox(core::rect<s32>(250,4,350,23), bar, GUI_ID_TEXTUREFILTER);     //添加下拉列表项目     box->addItem(L"No filtering");     box->addItem(L"Bilinear");     box->addItem(L"Trilinear");     box->addItem(L"Anisotropic");     box->addItem(L"Isotropic");     还有个天空盒场景节点在前面的例子中也没有出现过。在场景中,最远处的景色因距离太远,一般不会随摄像机的移动而发生改变,因此最远处的场景可以用静态的几幅图片来代替3D模型。在场景的最远处建立一个巨大的立方体或球体,把所有3D模型包围在这个几何体内,然后将远处场景的图片绑定到这个几何体上,摄像机在场景内移动,就能以最小的开销看到最远方法的景色,这就是天空盒或天空球。不过球体的贴图纹理一般不容易做,因此大部分时候见到的是使用天空盒。在irr里,制作天空盒的过程引擎已经帮实现好了,只需在场景管理器里调用addSkyBoxSceneNode方法,并设置好天空盒6个面的纹理就能把天空盒添加到场景里。     //添加一个天空盒场景节点。     SkyBox = smgr->addSkyBoxSceneNode(driver->getTexture("irrlicht2_up.jpg"),driver->getTexture("irrlicht2_dn.jpg"),driver->getTexture("irrlicht2_lf.jpg"),    driver->getTexture("irrlicht2_rt.jpg"),driver->getTexture("irrlicht2_ft.jpg"),driver->getTexture("irrlicht2_bk.jpg"));          这个例子真够长啊,竟然总结了好久。写完竟然到光棍节了。//(ㄒoㄒ)// 这里祝各位跟我一样的光棍早日脱光!光棍节快乐!

返回顶部
查看电脑版