Irrlicht之03.CustomSceneNode
这个例子里演示了如何手工创建(而非从文件加载)一个场景节点,并在引擎 中将其进行显示。
/*
这个例子是为更高级一些的开发者设计的,如果你仅仅是简单的了解Irr引擎,
请优先看看其他的例子。
这个例子里演示了如何手工创建(而非从文件加载)一个场景节点,并在引擎
中将其进行显示。首先,我们需要一个手工创建的场景节点。如果你想实现一个
渲染技巧,Irr引擎并不支持的时候,你就必须自己手工创建了。
例如,你想在一个大场景地形节点中增加一个户内场景的入口时,就可以自己
手工创建节点了。在创建这些自定义节点时,你会发现你能够很轻松的对Irr引擎
进行延伸,使其适合你的需求。
在这个例子以至今后的例子中我依旧保持我的简朴风格:保证代码的简洁,
并将一切代码都仅写在一个.cpp源文件中。
最开始,依旧是包含头文件以及链接库,声明命名空间。
*/
#include <irrlicht.h>
#include <iostream>
using namespace irr;
#pragma comment(lib, "Irrlicht.lib")
/*
从这里开始进入了例子中最重要的部分:
我们自己定义的场景节点类。为了简单,我们不象前文所说,在巨大的场景地形
中增加一个户内场景入口了,我们仅简单的做一个4个顶点连接出3D物体,
虽然这个显的很简单,实际上两者原理是一样的,我们也仅仅负责将3D物绘制出来,
并不做其他逻辑。
当我们自己定制的场景节点插入Irr引擎场景吧。
首先我们将自定义场景节点继承于ISceneNode接口,并重载其一些函数。
*/
class CSampleSceneNode : public scene::ISceneNode
{
/*
首先,我们声明一些成员变量,开辟一些空间来存储数据。包括:
1个包围盒,4个顶点,以及3D物体的材质。
*/
core::aabbox3d<f32> Box;
video::S3DVertex Vertices[4];
video::SMaterial Material;
/*
构造函数的参数包含了:场景节点的父节点指针,场景管理器的指针,
以及一个场景节点的ID号。
在构造函数内部,我们调用了父类的构造函数,设置了一些我们绘制场景节点
和创建3D物体的材质和顶点等属性。
*/
public:
CSampleSceneNode(scene::ISceneNode* parent, scene::ISceneManager* mgr, s32 id)
: scene::ISceneNode(parent, mgr, id)
{
Material.Wireframe = false;
Material.Lighting = false;
Vertices[0] = video::S3DVertex(0,0,10, 1,1,0, video::SColor(255,0,255,255), 0, 1);
Vertices[1] = video::S3DVertex(10,0,-10, 1,0,0, video::SColor(255,255,0,255), 1, 1);
Vertices[2] = video::S3DVertex(0,20,0, 0,1,1, video::SColor(255,255,255,0), 1, 0);
Vertices[3] = video::S3DVertex(-10,0,-10, 0,0,1, video::SColor(255,0,255,0), 0, 0);
/*
Irr引擎需要你定义的场景节点的包围盒。它将会使用这个包围盒进行一些自动
的剪裁等工作。因此我们需要使用3D物体的4个顶点来创建他。
如果你不希望引擎使用包围盒进行自动剪裁,或者不想创建包围盒,你可以这么写
AutomaticCullingEnabled = false;它可以帮你关闭包围盒。
*/
Box.reset(Vertices[0].Pos);
for (s32 i=1; i<4; ++i)
Box.addInternalPoint(Vertices[i].Pos);
}
/*
在绘制之前,Irr的场景管理器会调用每个场景节点的OnRegisterSceneNode()方法。
如果你想场景能被自动加载,那么你就将它注册到场景管理器中,它就会在场景管理器
进行Render的时候被自动调用绘制。
那么引擎为什么不默认的将全部场景节点都注册进去呢?
这是因为渲染顺序不好决定的原因,个别时间还是需要用户进行特殊的设置。
例如,在一般的场景节点被渲染的时候,深度缓冲区的阴影需要在其他所有场景节点
渲染之后进行绘制, 或者象摄象机,光线场景节点这些却需要在其他普通场景节点
渲染之前进行渲染。
不过我们在这里仅需要普通渲染,所以可以直接将其注册到场景管理器中去。
如果我们需要提前或拖后进行渲染的话,可以这么写
SceneManager->registerNodeForRendering(this, SNRT_LIGHT_AND_CAMERA);
这样就是告诉场景管理器,我们这次注册的场景节点,是和灯光和摄象机一样需要
提前渲染的。
值得庆幸的是在本场景节点被注册的同时,它的所有子节点也会默认的得到注册。
*/
virtual void OnRegisterSceneNode()
{
if (IsVisible)
SceneManager->registerNodeForRendering(this);
ISceneNode::OnRegisterSceneNode();
}
/*
在render()方法中,最有趣的事情发生了:场景节点开始渲染它们自己了。
我们重载这个了这个函数进行3D物体的绘制工作。
In the render() method most of the interesting stuff happenes: The
Scene node renders itself. We override this method and draw the
tetraeder.
*/
virtual void render()
{
u16 indices[] = { 0,2,3, 2,1,3, 1,0,3, 2,0,1 };
video::IVideoDriver* driver = SceneManager->getVideoDriver();
driver->setMaterial(Material);
driver->setTransform(video::ETS_WORLD, AbsoluteTransformation);
driver->drawIndexedTriangleList(&Vertices[0], 4, &indices[0], 4);
}
/*
我们在重载ISence之外,至少还是新创建了三个额外的方法。
GetBoundingBox()可以获得场景节点的包围盒
GetMaterialCount()返回了这个场景节点的材质数量。(我们这个3D物体仅一套材质)
getMaterial()返回了指定编号的材质。(因为在这里我们只有一个材质,
我们可以return Material;这样写下去,并且在本代码中,该函数的参数也
不应当大于0,我们只有一个材质嘛,编号最大当然是0咯)
*/
virtual const core::aabbox3d<f32>& getBoundingBox() const
{
return Box;
}
virtual u32 getMaterialCount()
{
return 1;
}
virtual video::SMaterial& getMaterial(u32 i)
{
return Material;
}
};
/*
就这样,我们自定义的场景节点就OK了,现在开始我们运行引擎,
来创建这个场景节点和一个摄象机,再看看结果。
*/
int main()
{
// 首先允许用户选择设备类型
video::E_DRIVER_TYPE driverType;
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 i;
std::cin >> i;
switch(i)
{
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 0;
}
// 创建一个Irr设备
IrrlichtDevice *device =
createDevice(driverType, core::dimension2d<s32>(640, 480), 16, false);
if (device == 0)
return 1;
// 获取视频驱动和场景管理器指针,并创建摄象机。
device->setWindowCaption(L"Custom Scene Node - Irrlicht Engine Demo");
video::IVideoDriver* driver = device->getVideoDriver();
scene::ISceneManager* smgr = device->getSceneManager();
smgr->addCameraSceneNode(0, core::vector3df(0,-40,0), core::vector3df(0,0,0));
/*
创建我们的场景节点,注意!在我们创建完它之后立刻就释放掉了drop()。
这并没有错误,是合法的。因为我们的场景管理器已经控制了它.
当然,你怕不安全还需要用,非要在代码最后(设备释放之前)
再drop()它,也没有什么问题。
*/
CSampleSceneNode *myNode =
new CSampleSceneNode(smgr->getRootSceneNode(), smgr, 666);
myNode->drop();
/*
现在你应该知道了如何创建一个我们自定义的场景节点了吧,并且让它象其他的
场景节点一样被渲染绘制。为了让画面更有意思一些,我们为场景节点加了个旋转动画器①。
让我们的3D物体所在场景节点旋转起来吧。
①笃志注:意译为动画器,原文是RotationAnimator鼓舞者。
*/
scene::ISceneNodeAnimator* anim =
smgr->createRotationAnimator(core::vector3df(0.8f, 0, 0.8f));
myNode->addAnimator(anim);
anim->drop();
/*
开始进行绘制,并显示FPS
*/
u32 frames=0;
while(device->run())
{
driver->beginScene(true, true, video::SColor(0,100,100,100));
smgr->drawAll();
driver->endScene();
if (++frames==100)
{
core::stringw str = L"Irrlicht Engine [";
str += driver->getName();
str += L"] FPS: ";
str += (s32)driver->getFPS();
device->setWindowCaption(str.c_str());
frames=0;
}
}
/*
释放设备,例子结束
*/
device->drop();
return 0;
}