Irrlicht学习备忘录——9 Meshviewer

9 Meshviewer

    官方代码($sdk)\examples\09.Meshviewer

Irrlicht学习备忘录——9 Meshviewer_第1张图片

    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         {
            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 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(600,45,800,480),false, L"Toolset", 0, GUI_ID_DIALOG_ROOT_WINDOW);
    //创建标签控制元素
    IGUITabControl* tab = env->addTabControl(core::rect(2,20,800-602,480-7), wnd, true, true);
    //创建一个标签页面
    IGUITab* t1 = tab->addTab(L"Config");
    //添加一个静态文本框到标签页面
    env->addStaticText(L"Scale:",core::rect(10,20,60,45), false, false, t1);
    //添加一个文本编辑框到标签页面
    env->addEditBox(L"1.0", core::rect(40,46,130,66), true, t1, GUI_ID_X_SCALE);
    //添加一个按钮到标签页面
    env->addButton(core::rect(10,134,85,165), t1, GUI_ID_BUTTON_SET_SCALE,L"Set");
    //添加一个滑动条
    IGUIScrollBar* scrollbar = env->addScrollBar(true,core::rect(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(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ㄒ)// 这里祝各位跟我一样的光棍早日脱光!光棍节快乐!

你可能感兴趣的:(irrlicht)