射线使用-碰撞检测
我们首先实现对鼠标右键的响应是他能够自由查看
然后实现摄像机不能穿越地面
最后单击左键增加实体,并能在单机状态下移动实体
首先,一如既往,提供程序运行的代码框架
#include <CEGUI/CEGUISystem.h>
#include <CEGUI/CEGUISchemeManager.h>
#include <OgreCEGUIRenderer.h>
#include "ExampleApplication.h"
class MouseQueryListener : public ExampleFrameListener, public OIS::MouseListener
{
public:
MouseQueryListener(RenderWindow* win, Camera* cam, SceneManager *sceneManager, CEGUI::Renderer *renderer)
: ExampleFrameListener(win, cam, false, true), mGUIRenderer(renderer)
{
} // MouseQueryListener
~MouseQueryListener()
{
}
bool frameStarted(const FrameEvent &evt)
{
return ExampleFrameListener::frameStarted(evt);
}
bool mouseMoved(const OIS::MouseEvent &arg)
{
return true;
}
bool mousePressed(const OIS::MouseEvent &arg, OIS::MouseButtonID id)
{
return true;
}
bool mouseReleased(const OIS::MouseEvent &arg, OIS::MouseButtonID id)
{
return true;
}
protected:
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
SceneManager *mSceneMgr; // A pointer to the scene manager
SceneNode *mCurrentObject; // The newly created object
CEGUI::Renderer *mGUIRenderer; // CEGUI renderer
};
class MouseQueryApplication : public ExampleApplication
{
protected:
CEGUI::OgreCEGUIRenderer *mGUIRenderer;
CEGUI::System *mGUISystem; // cegui system
public:
MouseQueryApplication()
{
}
~MouseQueryApplication()
{
}
protected:
void chooseSceneManager(void)
{
// Use the terrain scene manager.
mSceneMgr = mRoot->createSceneManager(ST_EXTERIOR_CLOSE);
}
void createScene(void)
{
}
void createFrameListener(void)
{
mFrameListener = new MouseQueryListener(mWindow, mCamera, mSceneMgr, mGUIRenderer);
mFrameListener->showDebugOverlay(true);
mRoot->addFrameListener(mFrameListener);
}
};
#if OGRE_PLATFORM == PLATFORM_WIN32 || OGRE_PLATFORM == OGRE_PLATFORM_WIN32
#define WIN32_LEAN_AND_MEAN
#include "windows.h"
INT WINAPI WinMain(HINSTANCE hInst, HINSTANCE, LPSTR strCmdLine, INT)
#else
int main(int argc, char **argv)
#endif
{
// Create application object
MouseQueryApplication app;
try {
app.go();
} catch(Exception& e) {
#if OGRE_PLATFORM == OGRE_PLATFORM_WIN32
MessageBox(NULL, e.getFullDescription().c_str(), "An exception has occurred!", MB_OK | MB_ICONERROR | MB_TASKMODAL);
#else
fprintf(stderr, "An exception has occurred: %s/n",
e.getFullDescription().c_str());
#endif
}
return 0;
}
运行结果应当是一片漆黑
一、创建场景(createScene()添加代码)
// Set ambient light创建天空
mSceneMgr->setAmbientLight(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(Degree(-30));
mCamera->yaw(Degree(-45));
既然我们建立了基本的世界空间,那么就要打开光标。打开光标,要使用CEGUI函数调用。
创建一个OgreCEGUIRenderer,然后创建系统对象并将刚创建的Renderer传给它。
创建mGUIRenderer时必须以最后一个参数告诉CEGUI你要用那个场景管理器
// CEGUI setup
mGUIRenderer = new CEGUI::OgreCEGUIRenderer(mWindow, Ogre::RENDER_QUEUE_OVERLAY, false, 3000, mSceneMgr);
mGUISystem = new CEGUI::System(mGUIRenderer);
// Mouse显示光标
CEGUI::SchemeManager::getSingleton().loadScheme((CEGUI::utf8*)"TaharezLookSkin.scheme");
CEGUI::MouseCursor::getSingleton().setImage("TaharezLook", "MouseArrow");
你可以运行一下你的程序,看到有天,有地,还有一个不会动的图形化光标
——————————————————————————————————————————————
二、将鼠标右键绑定到“鼠标观察”模式
首先进行变量介绍(这些变量框架代码已经提供了)
RaySceneQuery *mRaySceneQuery; // 射线场景查询指针,寻找地面上的坐标
bool mLMouseDown, mRMouseDown; // 如果按下鼠标按钮,返回True
int mCount; // 屏幕上机器人的数量,实体数
SceneManager *mSceneMgr; // 指向场景管理器的指针
SceneNode *mCurrentObject; //指向最近创建的场景节点的指针(我们将用这个“拖拽”实体)。
CEGUI::Renderer *mGUIRenderer; // CEGUI渲染器
MouseQueryListener的构造函数中添加代码:
// Setup default variables
mCount = 0;
mCurrentObject = NULL;
mLMouseDown = false;
mRMouseDown = false;
mSceneMgr = sceneManager;
// Reduce move speed
mMoveSpeed = 50;
mRotateSpeed /= 500;
//为了MouseQueryListener能收到鼠标事件,我们必须把它注册为一个鼠标监听器。
// Register this so that we get mouse events.
mMouse->setEventCallback(this);
//用场景管理器的一个调用创建一个RaySceneQuery对象
// Create RaySceneQuery
mRaySceneQuery = mSceneMgr->createRayQuery(Ray());
//创建一个RaySceneQuery,以后我们就必须销毁它。
~MouseQueryListener()析构函数增加代码:
// We created the query, and we are also responsible for deleting it.
mSceneMgr->destroyQuery(mRaySceneQuery);
mouseMoved中增加代码:
// Update CEGUI with the mouse motion
CEGUI::System::getSingleton().injectMouseMove(arg.state.X.rel, arg.state.Y.rel);
// If we are dragging the left mouse button.
if (mLMouseDown)
{
} // if
// If we are dragging the right mouse button.
else if (mRMouseDown)
{
mCamera->yaw(Degree(-arg.state.X.rel * mRotateSpeed));
mCamera->pitch(Degree(-arg.state.Y.rel * mRotateSpeed));
} // else if
mousePressed中增加代码:
//当鼠标右键按下时,隐藏光标,并设置变量mRMouseDown为true。
// 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
mouseReleased中增加代码:
//右键抬起时,我们需要再次显示光标,并将mRMouseDown设置为false。
// 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
试一下你的摄像机是否能受右键控制吧!
____________________________________________________________________________
三、地形碰撞检测
在BaseFrameListener移动了摄像机后,我们要确保摄像机在地面以上10个单位处。如果它不在,我们要把它移到那儿。
frameStarted中增加代码(删除原来的return语句):
// Process the base frame listener code. Since we are going to be
// manipulating the translate vector, we need this to happen first.
if (!ExampleFrameListener::frameStarted(evt))
return false;
//因为ExampleFrameListener的frameStarted成员函数移动摄像机,并且在此发生后我们需要在函数中安排
//我们的剩余行动。我们的目标及时找到摄像机的当前位置,并沿着它向地面发射一条射线。这被称为射线
//场景查询,它会告诉我们我们下面的地面的高度。得到了摄像机的当前位置后,我们需要创建一条射线。
//这条射线有一个起点(射线开始的地方),和一个方向(此处y轴负方向)。
// Setup the scene query
Vector3 camPos = mCamera->getPosition();
Ray cameraRay(Vector3(camPos.x, 5000.0f, camPos.z), Vector3::NEGATIVE_UNIT_Y);
//一旦我们创建了射线,我们就告诉RaySceneQuery对象使用它。
mRaySceneQuery->setRay(cameraRay);
//使用了5000.0f高度代替了摄像机的实际位置。
//现在我们需要执行查询,得到结果。查询结果是std::iterator类型的。
// Perform the scene query
RaySceneQueryResult &result = mRaySceneQuery->execute();
RaySceneQueryResult::iterator itr = result.begin();
//begin方法获得迭代器的第一个元素。如果result.begin() == result.end(),那么无返回结果
//保证至少返回一个查询结果(itr != result.end()),那个结果是地面(itr->worldFragment)。
// Get the results, set the camera height
if (itr != result.end() && itr->worldFragment)
{
//worldFragment结构包含有在变量singleIntersection(一个Vector3)中射线击中地面的位置。
//我们要得到地面的高度,依靠将这个向量的Y值赋值给一个本地变量。
//一旦我们有了高度,我们就要检查摄像机是否低于这一高度,如果低于这一高度,
//那么我们要将摄像机向上移动至地面高度。
//注意,我们实际将摄像机多移动了10个单位。这样保证我们不能由于太靠近地面而看穿地面。
Real terrainHeight = itr->worldFragment->singleIntersection.y;
if ((terrainHeight + 10.0f) > camPos.y)
mCamera->setPosition( camPos.x, terrainHeight + 10.0f, camPos.z );
}
return true;
运行一下,看你还能否“入地”!
_______________________________________________________________________________________
四、添加对象并实现拖拽
每次点击鼠标左键,我们将向屏幕上创建和添加对象。每次你点击、按住鼠标左键,就会创建一个对象并跟随你的光标。你可以移动对象,直到你松开鼠标左键,同时对象也锁定在那一点上。要做到这些,我们需要改变mousePressed函数。
在mousePressed()中的if语句添加代码:
// 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();
Ray mouseRay = mCamera->getCameraToViewportRay(mousePos.d_x/float(arg.state.width), mousePos.d_y/float(arg.state.height));
// Camera::getCameraToViewpointRay;一个将屏幕上的点击(X和Y坐标)转换成一条可供RaySceneQuery对象使用的射线的好用函数
mRaySceneQuery->setRay(mouseRay);
// Execute query执行查询
RaySceneQueryResult &result = mRaySceneQuery->execute();
RaySceneQueryResult::iterator itr = result.begin( );
// Get results, create a node/entity on the position
if (itr != result.end() && itr->worldFragment)
{
//有了worldFragment(也就是点击的位置),就可以创建对象并把它放到位。
//解决唯一命名问题
char name[16];
sprintf( name, "Robot%d", mCount++ );
//用itr->worldFragment->singleIntersection作为我们的机器人的默认位置
Entity *ent = mSceneMgr->createEntity(name, "robot.mesh");
mCurrentObject = mSceneMgr->getRootSceneNode()->createChildSceneNode(String(name) + "Node", itr->worldFragment->singleIntersection);
mCurrentObject->attachObject(ent);
mCurrentObject->setScale(0.1f, 0.1f, 0.1f);//地图挺小的放小一点
} // if
mLMouseDown = true;
} // if
在mousemoved()的if(mLMouseDown){}中添加代码:
if (mLMouseDown)
{
CEGUI::Point mousePos = CEGUI::MouseCursor::getSingleton().getPosition();
Ray mouseRay = mCamera->getCameraToViewportRay(mousePos.d_x/float(arg.state.width),mousePos.d_y/float(arg.state.height));
mRaySceneQuery->setRay(mouseRay);
RaySceneQueryResult &result = mRaySceneQuery->execute();
RaySceneQueryResult::iterator itr = result.begin();
if (itr != result.end() && itr->worldFragment)
mCurrentObject->setPosition(itr->worldFragment->singleIntersection);
} // if