这个例子告诉我们如何使用引擎去创建一个更加复杂的应用程序。我们使用Irr的场景管理器和用户界面API创建一个简单的Mesh查看器。

/*

这个例子告诉我们如何使用引擎去创建一个更加复杂的应用程序。我们使用Irr的场景管理器和用户界面API创建一个简单的Mesh查看器。

这个例子告诉我们如何去创建按钮,窗口,工具栏,菜单,下拉列表,编辑框,图象,消息窗口,天空盒,以及如何去使用Irr引擎中完善的XML阅读器进行解析一个XML文件。

我们依旧象其他的例子一样:包含所有必要的头文件,为编译器一个commment命令要求它链接正确的lib.之后我们定义了一些全局变量。同样的,我们还使用了两个"using namespace"声明来减少麻烦。

在本例中,我们使用了大量的GUI命名空间内的函数。
*/
#include <irrlicht.h>
#include <iostream>


using namespace irr;
using namespace gui;

#pragma comment(lib, "Irrlicht.lib")


IrrlichtDevice *Device = 0;
core::stringc StartUpModelFile;
core::stringw MessageText;
core::stringw Caption;
scene::IAnimatedMeshSceneNode* Model = 0;
scene::ISceneNode* SkyBox = 0;

scene::ICameraSceneNode* Camera[2] = { 0, 0};

/*
切换两种不同的摄象机
*/
void setActiveCamera ( scene::ICameraSceneNode* newActive )
{
if ( 0 == Device )
   return;

scene::ICameraSceneNode* active = Device->getSceneManager()->getActiveCamera ();

newActive->setInputReceiverEnabled ( true );
Device->getSceneManager()->setActiveCamera ( newActive );
}

/*
下面的三个函数做了几个Mesh查看器的功能。
第一个showAboutText()函数简单的显示了一个消息框,其标题和内容都在XML文件中进行动态读取。
这个文本将在启动时候被存储在MessageText,Caption两个变量中。
*/
void showAboutText()
{
// 创建一个模态消息窗口,其中信息从XML文件中读取
Device->getGUIEnvironment()->addMessageBox(
   Caption.c_str(), MessageText.c_str());
}


/*
第二个函数是loadModel(),它能读取一个模型并且使用场景管理器addAnimatedMeshSceneNode再将它显示出来。
这并不复杂是吧。当然,在模型无法读取时,会显示一个简单的警告消息窗口。
*/
void loadModel(const c8* fn)
{
// 根据文件格式进行区分处理

core::stringc filename ( fn );

core::stringc extension;
// 获取extension的后缀名
core::getFileNameExtension ( extension, filename );
// 把extension变成小写字母
extension.make_lower();

// 支持下列格式的纹理文件
if ( extension == ".jpg" ||
    extension == ".png" ||
    extension == ".tga" ||
    extension == ".pcx" ||
    extension == ".psd" ||
    extension == ".bmp"
   )
{
   video::ITexture * texture = Device->getVideoDriver()->getTexture( filename.c_str() );
   if ( texture && Model )
   {
    // 重新加载纹理
    Device->getVideoDriver()->removeTexture ( texture );
    texture = Device->getVideoDriver()->getTexture( filename.c_str() );

    Model->setMaterialTexture ( 0, texture );
   }
   return;
}

// 如果是一个压缩文件,则将它放置到文件管理器系统中进行处理
if ( extension == ".pk3" ||
    extension == ".zip"
   )
{
   Device->getFileSystem()->addZipFileArchive( filename.c_str () );
   return;
}

// 将模型读取到引擎中

if (Model)
   Model->remove();

Model = 0;

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;
}

// 设置默认材质参数

Model = Device->getSceneManager()->addAnimatedMeshSceneNode(m);
Model->setMaterialFlag(video::EMF_LIGHTING, false);
// Model->setMaterialFlag(video::EMF_BACK_FACE_CULLING, false);
Model->setDebugDataVisible(scene::EDS_OFF);
Model->setAnimationSpeed(30);
}


/*
最后,第三个函数创建了一个工具栏窗口。在这个简单的Mesh查看器中,
这个工具栏窗口仅仅包含一个制表子窗口,在其上包含三个改变模型缩放显示的编辑框。
*/
void createToolBox()
{
IGUIEnvironment* env = Device->getGUIEnvironment();
IGUIElement* root = env->getRootGUIElement();
IGUIElement* e = root->getElementFromId(5000, true);
// 为防止重复创建工具箱窗口,所以先进行安全删除
if (e) e->remove();

// 创建工具箱窗口
IGUIWindow* wnd = env->addWindow(core::rect<s32>(600,25,800,480),
   false, L"Toolset", 0, 5000);

// 创建制表窗口以及其中的制表
IGUITabControl* tab = env->addTabControl(
   core::rect<s32>(2,20,800-602,480-7), wnd, true, true);

IGUITab* t1 = tab->addTab(L"Scale");
IGUITab* t2 = tab->addTab(L"Test");

// 在一号制表上增加三个输入框和一个按钮
env->addEditBox(L"1.0", core::rect<s32>(40,50,130,70), true, t1, 901);
env->addEditBox(L"1.0", core::rect<s32>(40,80,130,100), true, t1, 902);
env->addEditBox(L"1.0", core::rect<s32>(40,110,130,130), true, t1, 903);

env->addButton(core::rect<s32>(10,150,100,190), t1, 1101, L"set");

// 增加一个无效的选择框
env->addCheckBox(true, core::rect<s32>(10,220,200,240), t1, -1, L"Senseless Checkbox");

// 增加一个透明度滚动条以及说明文字
env->addStaticText(L"Transparent Control:", core::rect<s32>(10,240,150,260), true, false, t1);
IGUIScrollBar* scrollbar = env->addScrollBar(true, core::rect<s32>(10,260,150,275), t1, 104);
scrollbar->setMax(255);

// 将Irr引擎Logo放置在最前方,因为,此时它可能被新创建的工具箱窗口遮挡住了。
// 译者注:作者原意是如上所说,将Irr引擎Logo移到顶层,防止被新建工具箱遮盖,然而其参数设置错误,
//         666并非Irr引擎Logo所在窗口ID编号,所以,实际上该效果没有被实现。
root->bringToFront(root->getElementFromId(666, true));
}


/*
为了获取GUI管理器的所有消息,我们需要创建一个事件接收器,这件事情很容易,如果有事件发生了,
根据事件呼叫者ID和事件类型进行处理就可以了。例如,一个ID为1000的Menu控件被选择到了,
则开启一个文件选择框。
*/
class MyEventReceiver : public IEventReceiver
{
public:
virtual bool OnEvent(SEvent event)
{
   // Esc键进行摄象机控制权更变
   if (event.EventType == EET_KEY_INPUT_EVENT &&
    event.KeyInput.Key == irr::KEY_ESCAPE &&
    event.KeyInput.PressedDown == false)
   {
    if ( Device )
    {
     scene::ICameraSceneNode * camera = Device->getSceneManager()->getActiveCamera ();
     if ( camera )
     {
      camera->setInputReceiverEnabled ( !camera->isInputReceiverEnabled() );
     }
     return true;
    }
   }

   if (event.EventType == EET_GUI_EVENT)
   {
    s32 id = event.GUIEvent.Caller->getID();
    IGUIEnvironment* env = Device->getGUIEnvironment();

    switch(event.GUIEvent.EventType)
    {
    case EGET_MENU_ITEM_SELECTED:
     {
      // 如果菜单中一个控件被点击

      IGUIContextMenu* menu = (IGUIContextMenu*)event.GUIEvent.Caller;
      s32 id = menu->getItemCommandId(menu->getSelectedItem());
     
      switch(id)
      {
      case 100: // File -> Open Model
       env->addFileOpenDialog(L"Please select a model file to open");
       break;
      case 101: // File -> Set Model Archive
       env->addFileOpenDialog(L"Please select your game archive/directory");
       break;
      case 200: // File -> Quit
       Device->closeDevice();
       break;
      case 300: // View -> Skybox
       SkyBox->setVisible(!SkyBox->isVisible());
       break;
      case 400: // View -> Debug Information
       if (Model)
        Model->setDebugDataVisible(Model->isDebugDataVisible() ? scene::EDS_OFF : scene::EDS_FULL);
       break;
      case 500: // Help->About
       showAboutText();
       break;
      case 610: // View -> Material -> Solid
       if (Model)
        Model->setMaterialType(video::EMT_SOLID);
       break;
      case 620: // View -> Material -> Transparent
       if (Model)
        Model->setMaterialType(video::EMT_TRANSPARENT_ADD_COLOR);
       break;
      case 630: // View -> Material -> Reflection
       if (Model)
        Model->setMaterialType(video::EMT_SPHERE_MAP);
       break;

      case 1000: // Camera -> Maya Style
       setActiveCamera ( Camera[0] );
       break;
      case 1100: // Camera -> First Person
       setActiveCamera ( Camera[1] );
       break;

      }
                    break;
     }

    case EGET_FILE_SELECTED:
     {
      // 在文件展开对话框中进行选择,读取一个模型文件
      IGUIFileOpenDialog* dialog =
       (IGUIFileOpenDialog*)event.GUIEvent.Caller;
      loadModel(core::stringc(dialog->getFilename()).c_str());
     }

    case EGET_SCROLL_BAR_CHANGED:

     // 控制UI的皮肤透明度
     if (id == 104)
     {
      s32 pos = ((IGUIScrollBar*)event.GUIEvent.Caller)->getPos();
      for (s32 i=0; i<irr::gui::EGDC_COUNT ; ++i)
      {
       video::SColor col = env->getSkin()->getColor((EGUI_DEFAULT_COLOR)i);
       col.setAlpha(pos);
       env->getSkin()->setColor((EGUI_DEFAULT_COLOR)i, col);
      }
     }
     break;

    case EGET_COMBO_BOX_CHANGED:

     // 控制反锯齿/纹理过滤模式
     if (id == 108)
     {
      s32 pos = ((IGUIComboBox*)event.GUIEvent.Caller)->getSelected();
      switch (pos)
      {
       case 0:
       if (Model)
       {
        Model->setMaterialFlag(video::EMF_BILINEAR_FILTER, false);
        Model->setMaterialFlag(video::EMF_TRILINEAR_FILTER, false);
        Model->setMaterialFlag(video::EMF_ANISOTROPIC_FILTER, false);
       }
       break;
       case 1:
       if (Model)
       {
        Model->setMaterialFlag(video::EMF_BILINEAR_FILTER, true);
        Model->setMaterialFlag(video::EMF_TRILINEAR_FILTER, false);
       }
       break;
       case 2:
       if (Model)
       {
        Model->setMaterialFlag(video::EMF_BILINEAR_FILTER, false);
        Model->setMaterialFlag(video::EMF_TRILINEAR_FILTER, true);
       }
       break;
       case 3:
       if (Model)
       {
        Model->setMaterialFlag(video::EMF_ANISOTROPIC_FILTER, true);
       }
       break;
       case 4:
       if (Model)
       {
        Model->setMaterialFlag(video::EMF_ANISOTROPIC_FILTER, false);
       }
       break;
      }
     }
     break;

    case EGET_BUTTON_CLICKED:

     switch(id)
     {
     case 1101:   // “set”按纽
      {
       // 设置缩放比例
       gui::IGUIElement* root = env->getRootGUIElement();
       core::vector3df scale;
       core::stringc s;

       s = root->getElementFromId(901, true)->getText();
       scale.X = (f32)atof(s.c_str());
       s = root->getElementFromId(902, true)->getText();
       scale.Y = (f32)atof(s.c_str());
       s = root->getElementFromId(903, true)->getText();
       scale.Z = (f32)atof(s.c_str());

       if (Model)
        Model->setScale(scale);
      }
      break;
     case 1102: // 菜单上的快捷按钮Open a model
      env->addFileOpenDialog(L"Please select a model file to open");
      break;
     case 1103: // 菜单上的快捷按钮Open Help
      showAboutText();
      break;
     case 1104: // 菜单上的快捷按钮Open ToolSet
      createToolBox();
      break;
     case 1105: // 菜单上的快捷按钮OSet module archive
      env->addFileOpenDialog(L"Please select your game archive/directory");
      break;
     }

     break;
    }
   }

   return false;
}
};


/*
最简单的工作已经做完了,我们仅需要创建一个Irr设备,再创建所有的按钮,菜单,工具拦就OK了。
我们象平常一样开动引擎,首先创建一个Irr设备。为了让我们的应用程序获取到消息,我们必须把
自己的事件接收器作为一个参数传入。
#ifdef WIN32这个预处理命令不是必要的,但是我包含了这句,目的是使我们的例子能够在非Windows
平台上也顺利使用DirectX.
译者注:本例中并未包含#ifdef WIN32该句,另外作者在本例中错误单词也较多,怀疑是喝高了或是心情不好吧。
如你所见,例子中使用了一个不常见的函数IrrlichtDevice::setResizeAble(). 这个函数允许
渲染窗口被拉伸缩短,这点在Mesh浏览器中是非常有用的。
*/
int main()
{
// 让用户选择设备类型
video::E_DRIVER_TYPE driverType = video::EDT_DIRECT3D8;

printf("Please select the driver you want for this example:\n"\
   " (a) Direct3D 9.0c\n (b) Direct3D 8.1\n (c) OpenGL 1.5\n"\
   " (d) Software Renderer\n (e) Burning's Software Renderer\n"\
   " (f) NullDevice\n (otherKey) exit\n\n");

char key;
std::cin >> key;

switch(key)
{
   case 'a': driverType = video::EDT_DIRECT3D9;break;
   case 'b': driverType = video::EDT_DIRECT3D8;break;
   case 'c': driverType = video::EDT_OPENGL;   break;
   case 'd': driverType = video::EDT_SOFTWARE; break;
   case 'e': driverType = video::EDT_BURNINGSVIDEO;break;
   case 'f': driverType = video::EDT_NULL;     break;
   default: return 1;
}

// 创建设备,如果失败则直接返回。

MyEventReceiver receiver;
Device = createDevice(driverType, core::dimension2d<s32>(800, 600),
   16, false, false, false, &receiver);

if (Device == 0)
   return 1;

Device->setResizeAble(true);

Device->setWindowCaption(L"Irrlicht Engine - Loading...");

video::IVideoDriver* driver = Device->getVideoDriver();
IGUIEnvironment* env = Device->getGUIEnvironment();
scene::ISceneManager* smgr = Device->getSceneManager();

driver->setTextureCreationFlag(video::ETCF_ALWAYS_32_BIT, true);

smgr->addLightSceneNode();
smgr->addLightSceneNode(0, core::vector3df(50,-50,100), video::SColorf(1.0f,1.0f,1.0f),20000);
// 设置我们的媒体目录,方便搜索
Device->getFileSystem()->addFolderFileArchive ( "../../media/" );

/*
   下一步就是读取配置文件了。它以XML格式存放着,恩,就象这样子:

   <?xml version="1.0"?>
   <config>
    <startUpModel file="some filename" />
    <messageText caption="Irrlicht Engine Mesh Viewer">
     Hello!
    </messageText>
   </config>

   我们需要在StartUpModelFile,MessageText,Caption这些全局变量中的数据,
   现在我们开始使用Irr引擎对XML文件进行解析读取
*/

// 从XML文件中读取配置

io::IXMLReader* xml = Device->getFileSystem()->createXMLReader(
   "config.xml");

while(xml && xml->read())
{
   switch(xml->getNodeType())
   {
   case io::EXN_TEXT:
    // 在这个XML文件中,只有唯一的正文信息,就是messageText
    MessageText = xml->getNodeData();
    break;
   case io::EXN_ELEMENT:
    {
     if (core::stringw("startUpModel") == xml->getNodeName())
      StartUpModelFile = xml->getAttributeValue(L"file");
     else
     if (core::stringw("messageText") == xml->getNodeName())
      Caption = xml->getAttributeValue(L"caption");
    }
    break;
   }
}

if (xml)
   xml->drop(); // 别忘记最后需要删除XML读取器

/*
   这些并不难是吧,现在我们将要设置一个好看的字体,并且创建一个菜单。
   我们必须为每个菜单项目创建一个子菜单,例如menu->addItem(L"File", -1, true, true);
   这样我们就创建了一个名叫File的子菜单,其ID为-1表示使用默认ID编号规则,其余的参数表意为
   这个子菜单控件是否可用,最后一个参数是说它本身也有自己的子控件,子菜单
   能够被menu->getSubMenu(0)这个函数获取,因为"File"这个子菜单的编号为0。
   注意:子菜单的默认编号规则是从0开始递增的。
*/

// 设置一个好看的字体

IGUISkin* skin = env->getSkin();
IGUIFont* font = env->getFont("fonthaettenschweiler.bmp");
if (font)
   skin->setFont(font);

// 创建菜单
gui::IGUIContextMenu* menu = env->addMenu();
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...", 100);
submenu->addItem(L"Set Model Archive...", 101);
submenu->addSeparator();
submenu->addItem(L"Quit", 200);

submenu = menu->getSubMenu(1);
submenu->addItem(L"toggle sky box visibility", 300);
submenu->addItem(L"toggle model debug information", 400);
submenu->addItem(L"model material", -1, true, true );

submenu = submenu->getSubMenu(2);
submenu->addItem(L"Solid", 610);
submenu->addItem(L"Transparent", 620);
submenu->addItem(L"Reflection", 630);

submenu = menu->getSubMenu(2);
submenu->addItem(L"Maya Style", 1000);
submenu->addItem(L"First Person", 1100);

submenu = menu->getSubMenu(3);
submenu->addItem(L"About", 500);

/*
   在菜单之下,我们需要一个工具栏。这个工具栏应该是一些进行贴图的按钮组成。
   另外我们创建一个下拉选框,恩,其中的选项就没有什么意义啦。
*/

// 创建工具栏

gui::IGUIToolBar* bar = env->addToolBar();

video::ITexture* image = driver->getTexture("open.png");
bar->addButton(1102, 0, L"Open a model",image, 0, false, true);

image = driver->getTexture("tools.png");
bar->addButton(1104, 0, L"Open Toolset",image, 0, false, true);

image = driver->getTexture("zip.png");
bar->addButton(1105, 0, L"Set Model Archive",image, 0, false, true);

image = driver->getTexture("help.png");
bar->addButton(1103, 0, L"Open Help", image, 0, false, true);

// 创建一个没有意义文字选项的下拉选框

gui::IGUIComboBox* box = env->addComboBox(core::rect<s32>(250,4,350,23), bar, 108);
box->addItem(L"No filtering");
box->addItem(L"Bilinear");
box->addItem(L"Trilinear");
box->addItem(L"Anisotropic");
box->addItem(L"Isotropic");

/*
   为了使编辑器看起来更好一点,我们设置了编辑器GUI的Aplha透明度,让它默认全不透明。
   并把制表窗口显示出来.另外,我们创建了一个显示当前桢率的文本。最后,
   改变一下窗口标题文字。
*/

// 设置编辑器的GUI部分为不透明

for (s32 i=0; i<gui::EGDC_COUNT ; ++i)
{
   video::SColor col = env->getSkin()->getColor((gui::EGUI_DEFAULT_COLOR)i);
   col.setAlpha(255);
   env->getSkin()->setColor((gui::EGUI_DEFAULT_COLOR)i, col);
}

// 创建一个制表窗口并将其显示

createToolBox();

// 创建FPS显示文字

IGUIStaticText* fpstext = env->addStaticText(L"", core::rect<s32>(400,4,570,23), true, false, bar);

// 设置窗口标题

Caption += " - [";
Caption += driver->getName();
Caption += "]";
Device->setWindowCaption(Caption.c_str());

/*
   这基本是上程序的全部了,我们在程序开始时把About帮助窗口显示出来,
   再读入一个默认的模型,再创建一个天空盒子,一个用户控制的摄象机,
   使程序看起来更加生动有趣一些。最后,在一个标准的绘制循环中一切被绘制出来。
*/

// 显示About这个帮助窗口
showAboutText();
// 读取一个默认的模型
loadModel(StartUpModelFile.c_str());

// 增加一个天空盒

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"));

// 增加一个摄象机场景结点
Camera[0] = smgr->addCameraSceneNodeMaya();
Camera[1] = smgr->addCameraSceneNodeFPS();

setActiveCamera ( Camera[0] );

// 读取Irr引擎Logo
IGUIImage *img =
   env->addImage(driver->getTexture("irrlichtlogo2.png"),
    core::position2d<s32>(10, driver->getScreenSize().Height - 64));

// 将Logo锁定到屏幕的左下方
img->setAlignment(EGUIA_UPPERLEFT, EGUIA_UPPERLEFT, EGUIA_LOWERRIGHT, EGUIA_LOWERRIGHT);

// 绘制一切

while(Device->run() && driver)
{
   if (Device->isWindowActive())
   {
    driver->beginScene(true, true, video::SColor(150,50,50,50));

    smgr->drawAll();
    env->drawAll();

    driver->endScene();

    core::stringw str(L"FPS: ");
    str.append(core::stringw(driver->getFPS()));
    str += L" Tris: ";
    str.append(core::stringw(driver->getPrimitiveCountDrawn()));
    fpstext->setText(str.c_str());
   }
   else
    Device->yield();
}

Device->drop();
return 0;
}