irrlicht之09.MeshViewer
这个例子告诉我们如何使用引擎去创建一个更加复杂的应用程序。我们使用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;
}