既然用了Ogre,那么一定是要有一个物理引擎的。最后选择了ODE,因为ODE在持续更新,文档稍微要多一些。
但是中文文档基本没有,为了以后能快速阅读,只好自己先翻一下了。
这只是一个介绍性的文档,其中的代码并不能形成一个完整的可执行程序。如果结合着OgreODE的Demo的代码看这篇文章,效果会很好的。
原文:http://www.ogre3d.org/wiki/index.php/First_steps_with_OgreODE
这是一个为OgreODE初学者所写的一个入门教程。因为这里(OGRE wiki)没有OgreODE的详尽的文档,所以写了这个教程,希望能对初学者有所指引和帮助。SimpleScenes是另一个学习的好地方,那里有7个带有实际代码的教程。本教程的所有代码都是从那7个教程里节选的。
一、初始化OgreODE,创建一个最简单的物理世界
在我们开始学习之前,你需要准备好:
1.带有一个sceneManager、一个camera、一些basic light的可工作的Ogre程序
2.OgreODE和ODE头文件
3.一个带有material andtexture 的 Ogre mesh,比如一个平面或者一条街道之类的(基础世界)
4.一个盒装的带有material andtexture 的 Ogre mesh(世界中的物体)
如果你学习完了Ogre Tutorials,那么你能很轻松地做到这些。
在一个最好是你申明了Ogre::Root的头文件里,包含上OgreODE的头文件,并申明以下变量:
#include "OgreOde\OgreOde_Core.h"
OgreOde::World *mWorld;
OgreOde::Space *mSpace;
OgreOde::StepHandler *mStepper;
OgreOde::World类似Ogre::Root,是OgreODE最顶层的对象。你所添加的每一个类,都会以某种方式添加到这个world中,因此这个世界中的物理定律就可以应用到添加的这个类上。但是要小心了,现在版本的OgreODE允许在一个程序中存在多个world。不过这种情况,只有在你确实需要复杂的"Prey-style"的重力效果时或做一些进阶性的优化时,才可能出现。
OgreOde::Space(碰撞空间)类似于一个所有对象(集合体更加特殊)的集合。这些对象可以与自己所在的space以及其它的space里的对象相碰撞,也可以不和任何对象发生碰撞。在大多数情况下,你将会创建一个有等级的碰撞空间——最重要节点的在顶部,很多次要的子节点附属在其下面。
在ODE里面有三种类型的碰撞空间——碰撞空间(Collision Space)、简单空间(Simple Space)和象限四分树空间(Quadtree Space)。碰撞空间和简单空间的差别很小(举个例子,设想碰撞空间是“根”空间,而简单空间是类似人物的手臂一样的“子”空间),而象限四分树空间是特别为大型项目的优化而设计的。所以如果你的场景有大于1000平方米的时候,象限四分树能提高10-20%的性能。(问题是,因为一些bug,象限四分树在OgreOde中还没有得到完好地实现,但这些bug是能被胸怀大志的开发者所修正的。)
OgreOde::StepHandler用于处理world中的时间,你将会使用它来更新事件(也就是说,stephandler时刻(比如每一帧)都在运行着,并且决定着哪一个物体运动到哪里以及与什么发生碰撞)。
二、改写你的.cpp文件
现在找到头文件所对应的cpp文件,让我们来创建一个华丽的新世界。
mWorld = new OgreOde::World(mSceneMgr);
mWorld->setGravity(Ogre::Vector3(0,-9.80665,0));
mWorld->setCFM(10e-5);
mWorld->setERP(0.8);
mWorld->setAutoSleep(true);
mWorld->setAutoSleepAverageSamplesCount(10);
mWorld->setContactCorrectionVelocity(1.0);
mSpace = mWorld->getDefaultSpace();
正如你所看到的,world注册到了场景管理器(scenemanager),同时设定了重力加速度为9.81。这里请注意两个事情:重力加速度设为了“负”,也就是说对象是“下落”而不是“上浮”;关于世界单位——如果你使用的是“1 ogre 单位 = 1 米”那么重力加速度应设为-9.80665,如果你使用的是“1 ogre 单位 = 1 厘米”(也就是说缩小了100倍)那么重力加速度就应该设为-980.665(相应扩大了100倍)。
后面的两行(setCFM 和 setERP)的设定,影响了模拟现实中的的关节、弹力和摩擦等方面。这里不详细讨论这两个函数,就像代码一样写就可以了。
setAutoSleep设定了world启用它的自动优化(即如果一个对象不再移动,它就会停止重复移动)。
setAutoSleepAverageSamplesCount,用于修正AutoSleep。最好就是设其参数为10,我们将在后面来讨论这个。
setContactCorrectionVelocity,一个很重要的设定。如果不设置它的话,对象间可能会相互穿透。
下一步,添加如下代码:
const Ogre::Real _time_step = 0.5;
const Ogre::Real time_scale = Ogre::Real(1.7);
const Ogre::Real max_frame_time = Ogre::Real(1.0 / 4);
mStepper = new OgreOde::StepHandler(mWorld,
OgreOde::StepHandler::QuickStep,
_time_step,
max_frame_time,
time_scale);
最后一步是设定Stepper。你可以在StepHandler::QuickStep,StepHandler::BasicStep 和StepHandler::FastStep中选择一种,但这里不做详细说明。你也可以在其它的不同类型的StepHandler中选择,但是这也是会在后面提到(尽管这个很重要)。
现在,我们有了一个带有重力的世界,但在这个世界里面什么东西都没有。让我们来改写它:
三、用一个盒子和一个平面来测试我们的重力系统
让我们在世界中添加一个平面和一个箱子(Crate)的实体/节点/主体,我们可以让这个箱子在我们设定的重力系统中进行自由落体运动。
在头文件中加入以下变量:
OgreOde::InfinitePlaneGeometry *mGround;
OgreOde::Body *mBody;
OgreOde::Geometry *mGeom;
OgreOde::BoxMass mMass;
Ogre::SceneNode *mNode;
Ogre::Entity *mEntity;
你应该已经知道最后两行的意思了。这些变量的第一个将会是ground上的平面,后面的三个变量是关于箱子的。
回到cpp文件,为创建平面添加如下代码:
mGround = new OgreOde::InfinitePlaneGeometry(
Plane(Ogre::Vector3(0,1,0),0),
mWorld, mWorld->getDefaultSpace());
// Use a load of meshes to represent the floor
int i = 0;
StaticGeometry* s;
s = mSceneMgr->createStaticGeometry("StaticFloor");
s->setRegionDimensions(Ogre::Vector3(160.0, 100.0, 160.0));
// Set the region origin so the center is at 0 world
s->setOrigin(Ogre::Vector3::ZERO);
for (Real z = -80.0;z <= 80.0;z += 20.0)
{
for (Real x = -80.0;x <= 80.0;x += 20.0)
{
String name = String("Ground") + StringConverter::toString(i++);
Entity* entity = mSceneMgr->createEntity(name, "plane.mesh");
entity->setQueryFlags (1<<4);
entity->setUserObject(mGround);
entity->setCastShadows(false);
s->addEntity(entity, Ogre::Vector3(x,0,z));
}
}
s->build();
这里创建的一个位于x-z面的平面。现在来创建箱子,首先我们需要常规的Ogre/Entity/Node:
mEntity = mSceneMgr->createEntity("crate","crateCube.mesh");
mEntity->setQueryFlags (1<<2);
mNode = mSceneMgr->getRootSceneNode()->createChildSceneNode("crate");
mNode->attachObject(mEntity);
mEntity->setNormaliseNormals(true);
mEntity->setCastShadows(true);
然后创建箱子的body并将其“注册”到world和场景节点(scene node):
mBody = new OgreOde::Body(mWorld);
mNode->attachObject(mBody);
然后我们设定箱子的体积和质量。因为这是一个盒装的对象,所以我们设定OgreOde::BoxGeometry。其它的比如Sphere或者TriangleMesh在OgreODE也同样有效。
Vector3 size(10.0,10.0,10.0);
OgreOde::BoxMass mMass(0.5,size);
mMass.setDensity(5.0,size);
mGeom = (OgreOde::Geometry*)new OgreOde::BoxGeometry(
size, mWorld, mSpace);
mNode->setScale(size.x * 0.1,size.y * 0.1,size.z * 0.1);
mBody->setMass(mMass);
mGeom->setBody(mBody);
mEntity->setUserObject(mGeom);
最后我们设定箱子的方向和位置。把它放置到摄像机(camera)前,方便看到它自由落体。
mBody->setOrientation(Quaternion(Radian(5.0),Ogre::Vector3(0,0,0)));
mBody->setPosition(Vector3(0,120,-20));
现在就有了一个悬在空中的箱子了。我们需要更新world的time steps。添加如下代码到主要的循环或gamestate::update 或你更新当前帧的地方:
Ogre::Real time = 0.1;
if (mStepper->step(time))
{
mWorld->synchronise();
}
如果你的代码里有一个类似"TimeSinceLastFrame"的值,设定"time"为这个值。现在就可以编译了,尝试去实现它!
如果你只看到一毫秒的急速运动的棕色的点,尝试将重力加速度除以100或1000,使用0.00981代替9.81。这个箱子应该会在这一点穿过平面,因为我们根本没有处理碰撞。关于碰撞的处理你可以在这里找到OgreOdeCollision Handling。