您的位置:首页 > 编程语言

Irrlicht学习备忘录——9 Meshviewer

2013-11-11 01:19 344 查看

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ㄒ)// 这里祝各位跟我一样的光棍早日脱光!光棍节快乐!
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息