英语水平有限,欢迎大家批评指正
本文并没有将原文全部翻译,只是将其中的一些知识点翻译总结了一下,想要查看详细讲解的话,可以到原文处看一下,附上英文原文地址:http://www.ogre3d.org/tikiwiki/tiki-index.php?page=Intermediate+Tutorial+2&structure=Tutorials
Setting up the Scene
添加如下代码到ITutorial02::createScene函数中:
// Set ambient light
mSceneMgr->setAmbientLight(Ogre::ColourValue(0.5, 0.5, 0.5));
mSceneMgr->setSkyDome(true, "Examples/CloudySky", 5, 8);
// World geometry
mSceneMgr->setWorldGeometry("terrain.cfg");
// Set camera look point
mCamera->setPosition(40, 100, 580);
mCamera->pitch(Ogre::Degree(-30));
mCamera->yaw(Ogre::Degree(-45));
现在我们已经建立起了一个基本的世界几何体(world geometry),还需要显示鼠标光标,为此我们调用CEGUI函数来实现。在此之前我们要用bootstrapSystem()函数启动CEGUI,注意它还让CEGUI使用了OGRE的资源管理系统:
// CEGUI setup
mGUIRenderer = &CEGUI::OgreRenderer::bootstrapSystem();
现在显示鼠标光标:
// Mouse CEGUI::SchemeManager::getSingleton().create((CEGUI::utf8*)"TaharezLook.scheme");
CEGUI::MouseCursor::getSingleton().setImage("TaharezLook", "MouseArrow");
编译运行程序,你将在屏幕中间看到一个光标,但它不会移动。你还需要告诉OGRE的资源管理器CEGUI的资源在哪里,如果你得到一个运行时异常,那么试一下把下面添加到resources.cfg中:
[CEGUI]
FileSystem=/usr/share/CEGUI/schemes
FileSystem=/usr/share/CEGUI/fonts
FileSystem=/usr/share/CEGUI/imagesets
FileSystem=/usr/share/CEGUI/layouts
FileSystem=/usr/share/CEGUI/looknfeel
FileSystem=/usr/share/CEGUI/lua_scripts
FileSystem=/usr/share/CEGUI/schemes
FileSystem=/usr/share/CEGUI/xml_schemas
Introducing the FrameListener
FrameListener是比较复杂的一段代码,下面我们概括一下我们要做什么,好让你有一个概念:
第一,我们要给鼠标右键绑定一个"鼠标查看"模式。如果不能使用鼠标来四处查看会很难受,所以我们的第一件事就是添加鼠标控制(只有当我们按下鼠标右键时)。
第二,让camera不能穿过地面。
第三,当我们点击鼠标左键时可以再地面上的任何位置添加实体。
最后,我们想要可以拖拽实体。按下鼠标左键不放,然后移动选择的实体到我们想要放置他的位置。松开鼠标,就会把实体放置该处。
为此我们要用几个protected变量:
Ogre::RaySceneQuery *mRaySceneQuery; // The ray scene query pointer
bool mLMouseDown, mRMouseDown; // True if the mouse buttons are down
int mCount; // The number of robots on the screen
Ogre::SceneNode *mCurrentObject; // The newly created object
CEGUI::Renderer *mGUIRenderer; // cegui renderer
float mRotateSpeed;
mRaySceneQuery变量保存了我们要用来寻找地面上的坐标的RaySceneQuery的复制。mLMouseDown和mRMouseDown变量用来判断鼠标是否被按下。mCount用来计数我们在屏幕上创建的实体的数量。mCurrentObject保存一个指向我们最近创建的场景节点的指针(我们要用他来拖拽实体)。mGUIRenderer保存一个指向用来更新CEGUI的CEGUI渲染器的指针。
Setting up the FrameListener
添加如下代码到createFrameListener方法的BaseApplication::createFrameListener()之后:
// Setup default variables
mCount = 0;
mCurrentObject = NULL;
mLMouseDown = false;
mRMouseDown = false;
// Reduce rotate speed
mRotateSpeed =.1;
最后,我们需要创建RaySceneQuery对象:
// Create RaySceneQuery
mRaySceneQuery = mSceneMgr->createRayQuery(Ogre::Ray());
这就是我们的createFrameListener所需要的,但如果我们创建一个RaySceneQuery,我们之后必须销毁它。添加如下代码到ITutorial02的析构函数:
// We created the query, and we are also responsible for deleting it.
mSceneMgr->destroyQuery(mRaySceneQuery);
Adding Mouse Look
我们要把鼠标查看模式绑定到鼠标右键上,为此我们要:
1.当鼠标移动时更新CEGUI
2.当鼠标被按下时设mRMouseDown为true
3.当鼠标白松开时设mRMouseDown 为false
4.当鼠标拖拽时改变view(视野)。
5.当鼠标拖拽时隐藏鼠标光标。
添加如下代码到ITutorial02::mousePressed:
// Left mouse button down
if (id == OIS::MB_Left)
{
mLMouseDown = true;
} // if
// Right mouse button down
else if (id == OIS::MB_Right)
{
CEGUI::MouseCursor::getSingleton().hide();
mRMouseDown = true;
} // else if
当鼠标右键被按下时光标隐藏,并设mRMouseDown为true。当鼠标右键松开时光标可见,并设mRMouseDown为false。添加如下代码到mouseReleased函数:
// Left mouse button up
if (id == OIS::MB_Left)
{
mLMouseDown = false;
} // if
// Right mouse button up
else if (id == OIS::MB_Right)
{
CEGUI::MouseCursor::getSingleton().show();
mRMouseDown = false;
} // else if
现在我们已经写好了所有的基础代码,我们想要当鼠标被按下右键不放而进行移动时改变view(视野)。我们要做的就是读取自上一次该方法被调用开始到现在,他被移动的距离。添加如下代码到ITutorial::mouseMoved函数中:
// If we are dragging the left mouse button.
if (mLMouseDown)
{
} // if
// If we are dragging the right mouse button.
else if (mRMouseDown)
{
mCamera->yaw(Ogre::Degree(-arg.state.X.rel * mRotateSpeed));
mCamera->pitch(Ogre::Degree(-arg.state.Y.rel * mRotateSpeed));
} // else if
Terrain Collision Detection
现在我们要使对象不能穿过地面,BaseApplication::createFrameListener函数已经处理了移动摄像机的操作,我们不动这些代码。当BaseApplication::createFrameListener函数移动摄像机后,我们要确保摄像机在地面之上10个单位以上。如果不是,我们就把它移到地面之上10个单位以上的位置。当本教程结束时,我们会用RaySceneQuery来做几件事情。
删除ITutorial02::frameRenderingQueued函数中的所有代码,添加如下代码:
// Process the base frame listener code. Since we are going to be
// manipulating the translate vector, we need this to happen first.
if (!BaseApplication::frameRenderingQueued(evt))
return false;
我们把这些代码添加到最上面,因为BaseApplication::frameRenderingQueued函数处理了TrayManager窗口的更新,且这之后我们在该函数中需要执行剩余的动作。我们的目的是找到摄像机当前的位置,并发射一个垂直向下穿过地面的射线(Ray)。这叫做射线场景查询(RaySceneQuery),它可以告诉我们距离地面的高度。找到摄像机当前的位置后,我们需要创建一个射线(Ray)。射线获取原点和方向,本例中方向为NEGATIVE_UNIT_Y,因为我们要射线垂直向下穿过地面。创建射线后,我们要告诉RaySceneQuery对象来使用:
// Setup the scene query
Ogre::Vector3 camPos = mCamera->getPosition();
Ogre::Ray cameraRay(Ogre::Vector3(camPos.x, 5000.0f, camPos.z), Ogre::Vector3::NEGATIVE_UNIT_Y);
mRaySceneQuery->setRay(cameraRay);
注意我们使用的射线高度为5000.0f,而不是摄像机的实际位置。如果我们使用摄像机的Y轴位置而不是这个高度,当摄像机是在地面下面时,我们可能会根本就找不到(miss entirely)地面。现在我们要执行查询并获取结果,结果以std::iterator的形式获得。
// Perform the scene query
Ogre::RaySceneQueryResult &result = mRaySceneQuery->execute();
Ogre::RaySceneQueryResult::iterator itr = result.begin();
查询结果基本上就是一个worldFragments(本例中就是地面Terrain)链表和一个movables链表。如果你对STL迭代器不是很熟悉,那么你只需要知道调用begin方法来获取迭代器的第一个元素。如果result.begin() == result.end(),那么就没有结果返回。下面的代码确保查询至少会返回一个结果(itr != result.end()),并且结果是地面(itr->worldFragment)。
// Get the results, set the camera height
if (itr != result.end() && itr->worldFragment)
{
worldFragments结构体包含着射线碰到地面的位置,这个位置保持在Vector3变量singleIntersection中。我们要通过把这个向量的Y值赋给一个本地向量来获取地面的高度。当我们得到了高度值,我们还要看一下摄像机是否在该高度之下,如果是我们要把摄像机移到这个高度。注意我们实际要把摄像机移动到地面之上10个单位,这确保当我们离地面太近时不至于让我们可以穿透地面。
Ogre::Real terrainHeight = itr->worldFragment->singleIntersection.y;
if ((terrainHeight + 10.0f) > camPos.y)
mCamera->setPosition( camPos.x, terrainHeight + 10.0f, camPos.z );
}
return true;
最后返回true来继续渲染。
Terrain Selection
这部分中,每次你点击鼠标左键,我们都会在屏幕上创建并添加一个对象。每次你点击并按住鼠标左键,一个对象被创建并"held" on your cursor。在你松开按键前,你都可以移动该对象。为此我们需要修改mousePressed函数,当你点击鼠标左键时做一些不同的事情。找到ITutorial02::mousePressed,我们要在这个if语句中添加代码。
// Left mouse button down
if (id == OIS::MB_Left)
{
mLMouseDown = true;
} // if
第一段代码看起来很熟悉,我们将创建一条射线(Ray)来让mRaySceneQuery 对象使用,并设置改射线。OGRE为我们提供了一个可以将屏幕上的点击变换为一条射线的Camera::getCameraToViewportRay函数,该射线可以让mRaySceneQuery对象来使用它。
// Left mouse button down
if (id == OIS::MB_Left)
{
// Setup the ray scene query, use CEGUI's mouse position
CEGUI::Point mousePos = CEGUI::MouseCursor::getSingleton().getPosition();
Ogre::Ray mouseRay = mCamera->getCameraToViewportRay(mousePos.d_x/float(arg.state.width), mousePos.d_y/float(arg.state.height));
mRaySceneQuery->setRay(mouseRay);
下面我们要执行查询并确保它返回一个结果:
// Execute query
Ogre::RaySceneQueryResult &result = mRaySceneQuery->execute();
Ogre::RaySceneQueryResult::iterator itr = result.begin( );
// Get results, create a node/entity on the position
if (itr != result.end() && itr->worldFragment)
{
现在我们有了worldFragment,我们要创建对象并把他放到该位置。我们的第一个难题是OGRE中的每个实体和场景节点都需要一个唯一的名字。为此我们要给每个实体、场景节点命名"Robot1", "Robot2", "Robot3"和"Robot1Node", "Robot2Node","Robot3Node"等, 首先我们创建名字:
char name[16];
sprintf( name, "Robot%d", mCount++ );
然后我们创建实体和场景节点。注意我们使用itr->worldFragment->singleIntersection 作为机器人的默认位置。我们还把他缩小到原来的1/10大小,因为地面很小。注意我们要把新创建的对象赋给成员变量mCurrentObject。我们在下一部分会用到。
Ogre::Entity *ent = mSceneMgr->createEntity(name, "robot.mesh");
mCurrentObject = mSceneMgr->getRootSceneNode()->createChildSceneNode(std::string(name) + "Node", itr->worldFragment->singleIntersection);
mCurrentObject->attachObject(ent);
mCurrentObject->setScale(0.1f, 0.1f, 0.1f);
} // if
mLMouseDown = true;
} // if
现在编译运行例子。你可以通过点击地面任意位置在屏幕上摆放机器人。我们基本上一届完成了程序,但我们还要完成对象拖拽的任务。添加代码到如下if语句中:
// If we are dragging the left mouse button.
if (mLMouseDown)
{
} // if
根据鼠标当前位置创建一条射线,然后执行一个射线场景查询(RaySceneQuery)并把对象移动到新的位置。注意我们不必检查mCurrentObject是否有效,因为如果mCurrentObject没有被mousePressed设置过mLMouseDown就不会为true。
if (mLMouseDown)
{
CEGUI::Point mousePos = CEGUI::MouseCursor::getSingleton().getPosition();
Ogre::Ray mouseRay = mCamera->getCameraToViewportRay(mousePos.d_x/float(arg.state.width),mousePos.d_y/float(arg.state.height));
mRaySceneQuery->setRay(mouseRay);
Ogre::RaySceneQueryResult &result = mRaySceneQuery->execute();
Ogre::RaySceneQueryResult::iterator itr = result.begin();
if (itr != result.end() && itr->worldFragment)
mCurrentObject->setPosition(itr->worldFragment->singleIntersection);
} // if
编译运行你的程序,我们已经完成了!