框选制作
首先,一如既往给出框架程序(运行该框架程序看到一个机器人的矩阵):
#include <CEGUI/CEGUI.h>
#include <OgreCEGUIRenderer.h>
#include "ExampleApplication.h"
class SelectionRectangle : public ManualObject
{
public:
SelectionRectangle(const String &name)
: ManualObject(name)
{
}
void setCorners(float left, float top, float right, float bottom)
{
}
void setCorners(const Vector2 &topLeft, const Vector2 &bottomRight)
{
setCorners(topLeft.x, topLeft.y, bottomRight.x, bottomRight.y);
}
};
class DemoListener : public ExampleFrameListener, public OIS::MouseListener
{
public:
DemoListener(RenderWindow* win, Camera* cam, SceneManager *sceneManager)
: ExampleFrameListener(win, cam, false, true), mSceneMgr(sceneManager), mSelecting(false)
{
mMouse->setEventCallback(this);
} // DemoListener
~DemoListener()
{
}
bool mouseMoved(const OIS::MouseEvent &arg)
{
CEGUI::System::getSingleton().injectMouseMove(arg.state.X.rel, arg.state.Y.rel);
return true;
}
bool mousePressed(const OIS::MouseEvent &arg, OIS::MouseButtonID id)
{
return true;
}
bool mouseReleased(const OIS::MouseEvent &arg, OIS::MouseButtonID id)
{
return true;
}
void performSelection(const Vector2 &first, const Vector2 &second)
{
}
void deselectObjects()
{
std::list<MovableObject*>::iterator itr;
for (itr = mSelected.begin(); itr != mSelected.end(); ++itr)
(*itr)->getParentSceneNode()->showBoundingBox(false);
}
void selectObject(MovableObject *obj)
{
obj->getParentSceneNode()->showBoundingBox(true);
mSelected.push_back(obj);
}
private:
Vector2 mStart, mStop;
SceneManager *mSceneMgr;
PlaneBoundedVolumeListSceneQuery *mVolQuery;
std::list<MovableObject*> mSelected;
SelectionRectangle *mRect;
bool mSelecting;
static void swap(float &x, float &y)
{
float tmp = x;
x = y;
y = tmp;
}
};
class DemoApplication : public ExampleApplication
{
public:
DemoApplication()
: mRenderer(0), mSystem(0)
{
}
~DemoApplication()
{
if (mSystem)
delete mSystem;
if (mRenderer)
delete mRenderer;
}
protected:
CEGUI::OgreCEGUIRenderer *mRenderer;
CEGUI::System *mSystem;
void createScene(void)
{
mRenderer = new CEGUI::OgreCEGUIRenderer(mWindow, Ogre::RENDER_QUEUE_OVERLAY, false, 3000, mSceneMgr);
mSystem = new CEGUI::System(mRenderer);
CEGUI::SchemeManager::getSingleton().loadScheme((CEGUI::utf8*)"TaharezLookSkin.scheme");
CEGUI::MouseCursor::getSingleton().setImage((CEGUI::utf8*)"TaharezLook", (CEGUI::utf8*)"MouseArrow");
mCamera->setPosition(-60, 100, -60);
mCamera->lookAt(60, 0, 60);
mSceneMgr->setAmbientLight(ColourValue::White);
for (int i = 0; i < 10; ++i)
for (int j = 0; j < 10; ++j)
{
Entity *ent = mSceneMgr->createEntity("Robot" + StringConverter::toString(i + j * 10), "robot.mesh");
SceneNode *node = mSceneMgr->getRootSceneNode()->createChildSceneNode(Vector3(i * 15, 0, j * 15));
node->attachObject(ent);
node->setScale(0.1, 0.1, 0.1);
}
}
void createFrameListener(void)
{
mFrameListener = new DemoListener(mWindow, mCamera, mSceneMgr);
mFrameListener->showDebugOverlay(true);
mRoot->addFrameListener(mFrameListener);
}
};
#if 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
DemoApplication app;
try {
app.go();
} catch(Exception& e) {
#if OGRE_PLATFORM == OGRE_PLATFORM_WIN32
MessageBoxA(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;
}
本节通过矩形来建立选取,因而必须先介绍一个相应的类。
创建网格的基本知识:
mesh的组成:顶点缓存(Vertex buffer)和索引缓存(Index buffer)。
顶点缓存在3D空间里定义点集。顶点缓存里的每一个元素由若干你能设置的属性来定义。唯一一个你必须设置的属性就是顶点的坐标。除了这个,你还有设置其它可选的属性,比如顶点颜色、纹理坐标等。你实际需要的属性取决于mesh的用途。
索引缓存通过从顶点缓存选取顶点,以“把点连起来”。索引缓存里每三个顶点定义了一个由GPU绘制的三角形。你在索引缓存里选取顶点的顺序,告诉了显卡这个三角形的朝向。逆时针绘制的三角形是朝向你的,顺时针绘制的三角形是背向你的。一般情况下只有三角形的正面才被绘制,所以确保你的三角形被正确载入是很重要的。
注意:
1、所有的mesh都有顶点缓存,但不一定都有索引缓存。比如一个空的三角形
2、顶点缓存和索引缓存通常保存在显卡自己的内存里,所以你的软件只要发送一些离散的命令,告诉它使用预定义的缓存来一口气渲染整个3D网格。
在Ogre里有两种方法来创建你自己的网格。第一种是继承SimpleRenderable,并直接提供给它顶点和索引缓存。这是最直接的创建方式,但也是最不直观的。为了使事情更简单,Ogre提供一个更棒的接口叫做ManualObject,它能让你用一些简单的函数来定义一个网格。你仅仅调用"position"和"colour"函数,而不用往缓存里丢位置、颜色等数据。
下面开始添加代码:
一、构建矩形
对创建的矩形的要求:
1、2D的矩形
2、发生重叠是矩形一定要在最上层。
SelectionRectangle的构造器函数中添加代码:
setRenderQueueGroup(RENDER_QUEUE_OVERLAY);//把这个物体的渲染队列设
//置成重叠队列(Overlay queue)。
//函数把投影矩阵(projection matrix)和视图矩阵(view matrix)
//设置成identity,作用就是声明做的东西是2D的
setUseIdentityProjection(true);
setUseIdentityView(true);
//把这个物体的查询标记设置成0
setQueryFlags(0);
setCorners里添加代码:
//坐标转换,构造矩形时,我们通过鼠标坐标(0-1)构建,也就是说这个数字转换成范围[-1,1]的
left = left * 2 - 1;
right = right * 2 - 1;
top = 1 - top * 2;
bottom = 1 - bottom * 2;
//开始创建对象
clear();//重新绘制矩形之前移除上次的矩形
begin("", RenderOperation::OT_LINE_STRIP);//第一个参数是材质,第二个参数是渲染操作
//可以使用点、线、三角形来渲染这个网格。如果我们要渲染一个实心的网格,可以用三角形。
//但我们只需要一个空的矩形,所以我们使用线条(line strip)。
//定义5个点(第一个和最后一个是相同的,这样才能连接成整个矩形):
position(left, top, -1);
position(right, top, -1);//Z参数设成-1,因为我们只定义一个2D对象而不必使用Z轴。
position(right, bottom, -1);
position(left, bottom, -1);
position(left, top, -1);
end();
AxisAlignedBox box;
box.setInfinite();
setBoundingBox(box);
________________________________________试运行程序,保证可以运行,但现在并无效果
体积选取
创建一个SelectionRectangle类的实例,然后让SceneManager来为我们创建一个体积查询:
DemoListener的构造函数里添加代码:
mRect = new SelectionRectangle("Selection SelectionRectangle");
mSceneMgr->getRootSceneNode()->createChildSceneNode()->attachObject(mRect);
mVolQuery = mSceneMgr->createPlaneBoundedVolumeQuery(PlaneBoundedVolumeList());
~DemoListener()里添加代码:
{
mSceneMgr->destroyQuery(mVolQuery);
delete mRect;
}
鼠标处理
mousePressed函数里添加代码:
if (id == OIS::MB_Left)
{
CEGUI::MouseCursor *mouse = CEGUI::MouseCursor::getSingletonPtr();
mStart.x = mouse->getPosition().d_x / (float)arg.state.width;
mStart.y = mouse->getPosition().d_y / (float)arg.state.height;
mStop = mStart;
mSelecting = true;
mRect->clear();
mRect->setVisible(true);
}
使用的是CEGUI::MouseCursor的x和y坐标,而不是OIS的鼠标坐标。这是因为有时OIS反映的坐标与CEGUI实际显示的不一样。为了保证我们与用户所看到的相一致,我们使用CEGUI的鼠标坐标。
mouseReleased函数里添加代码:
if (id == OIS::MB_Left)
{
performSelection(mStart, mStop);
mSelecting = false;
mRect->setVisible(false);
}
mouseMoved函数里添加代码:
if (mSelecting)
{
CEGUI::MouseCursor *mouse = CEGUI::MouseCursor::getSingletonPtr();
mStop.x = mouse->getPosition().d_x / (float)arg.state.width;
mStop.y = mouse->getPosition().d_y / (float)arg.state.height;
mRect->setCorners(mStart, mStop);
}//鼠标移动时,我们都调整mStop向量,这样我们就能轻松地使用setCorners成员函数了
运行你的程序拖动鼠标左键,看看效果吧!
——————————————————————————————————————————————
performSelection里添加代码:
float left = first.x, right = second.x,
top = first.y, bottom = second.y;
if (left > right)
swap(left, right);
if (top > bottom)
swap(top, bottom);
分别为left、right、top、botton变量赋予向量参数。if语句保证了我们实际的left和top值最小。(如果这个矩形是“反向”画出来的,意味着从右下角到左上角,我们就要进行这种交换。)
如果这个矩形小于屏幕的某个百分比,我们只将它返回而不执行这个选取。
if ((right - left) * (bottom - top) < 0.0001)
return;
PlaneBoundedVolumeQueries使用平面来包围一个区域,所以所有在区域里的物体都被选取。我们将创建一个被五个平面包围的区域,它是朝向里面的。为了创建这些平面,我们建立了4条射线,每一条都是矩形的一个角产生的。
Ray topLeft = mCamera->getCameraToViewportRay(left, top);
Ray topRight = mCamera->getCameraToViewportRay(right, top);
Ray bottomLeft = mCamera->getCameraToViewportRay(left, bottom);
Ray bottomRight = mCamera->getCameraToViewportRay(right, bottom);
着射线走100个单位抓取一个点。这是随便选择的。在这里唯一重要的是前平面,它在摄像机前面3个单位的位置。
PlaneBoundedVolume vol;
vol.planes.push_back(Plane(topLeft.getPoint(3),
topRight.getPoint(3), bottomRight.getPoint(3))); // 前平面
vol.planes.push_back(Plane(topLeft.getOrigin(),
opLeft.getPoint(100), topRight.getPoint(100))); // 顶平面
vol.planes.push_back(Plane(topLeft.getOrigin(),
bottomLeft.getPoint(100), topLeft.getPoint(100))); // 左平面
vol.planes.push_back(Plane(topLeft.getOrigin(),
bottomRight.getPoint(100), bottomLeft.getPoint(100))); // 底平面
vol.planes.push_back(Plane(topLeft.getOrigin(),
topRight.getPoint(100), bottomRight.getPoint(100))); // 右平面
//定义了一个在摄像机前无限伸展的“开放盒子”。
我们还需要执行这个查询:
PlaneBoundedVolumeList volList;
volList.push_back(vol);
mVolQuery->setVolumes(volList);
SceneQueryResult result = mVolQuery->execute();
处理查询返回的结果。首先我们要取消所有先前选取的物体,然后选取所有查询得到的物体。
deselectObjects();
SceneQueryResultMovableList::iterator itr;
for (itr = result.movables.begin(); itr != result.movables.end(); ++itr)
selectObject(*itr);
ok,运行你的程序你已经可以实现框选了。
_____________________________________________________________________________________________
框选制作(手札24 终级代码)
#include <CEGUI/CEGUI.h>
#include <OgreCEGUIRenderer.h>
#include "ExampleApplication.h"
class SelectionRectangle : public ManualObject
{
public:
SelectionRectangle(const String &name)
: ManualObject(name)
{
setRenderQueueGroup(RENDER_QUEUE_OVERLAY);//把这个物体的渲染队列设置成重叠队列(Overlay queue)。
//函数把投影矩阵(projection matrix)和视图矩阵(view matrix)设置成identity,作用就是声明做的东西是2D的
setUseIdentityProjection(true);
setUseIdentityView(true);
//把这个物体的查询标记设置成0
setQueryFlags(0);
}
void setCorners(float left, float top, float right, float bottom)
{
//坐标转换,构造矩形时,我们通过鼠标坐标(0-1)构建,也就是说这个数字转换成范围[-1,1]的
left = left * 2 - 1;
right = right * 2 - 1;
top = 1 - top * 2;
bottom = 1 - bottom * 2;
//开始创建对象
clear();//重新绘制矩形之前移除上次的矩形
begin("", RenderOperation::OT_LINE_STRIP);//第一个参数是材质,第二个参数是渲染操作
//可以使用点、线、三角形来渲染这个网格。如果我们要渲染一个实心的网格,可以用三角形。
//但我们只需要一个空的矩形,所以我们使用线条(line strip)。
//定义5个点(第一个和最后一个是相同的,这样才能连接成整个矩形):
position(left, top, -1);
position(right, top, -1);//Z参数设成-1,因为我们只定义一个2D对象而不必使用Z轴。
position(right, bottom, -1);
position(left, bottom, -1);
position(left, top, -1);
end();
AxisAlignedBox box;
box.setInfinite();
setBoundingBox(box);
}
void setCorners(const Vector2 &topLeft, const Vector2 &bottomRight)
{
setCorners(topLeft.x, topLeft.y, bottomRight.x, bottomRight.y);
}
};
class DemoListener : public ExampleFrameListener, public OIS::MouseListener
{
public:
DemoListener(RenderWindow* win, Camera* cam, SceneManager *sceneManager)
: ExampleFrameListener(win, cam, false, true), mSceneMgr(sceneManager), mSelecting(false)
{
mMouse->setEventCallback(this);
mRect = new SelectionRectangle("Selection SelectionRectangle");
mSceneMgr->getRootSceneNode()->createChildSceneNode()->attachObject(mRect);
mVolQuery = mSceneMgr->createPlaneBoundedVolumeQuery(PlaneBoundedVolumeList());
} // DemoListener
~DemoListener()
{
mSceneMgr->destroyQuery(mVolQuery);
delete mRect;
}
bool mouseMoved(const OIS::MouseEvent &arg)
{
CEGUI::System::getSingleton().injectMouseMove(arg.state.X.rel, arg.state.Y.rel);
if (mSelecting)
{
CEGUI::MouseCursor *mouse = CEGUI::MouseCursor::getSingletonPtr();
mStop.x = mouse->getPosition().d_x / (float)arg.state.width;
mStop.y = mouse->getPosition().d_y / (float)arg.state.height;
mRect->setCorners(mStart, mStop);
}
return true;
}
bool mousePressed(const OIS::MouseEvent &arg, OIS::MouseButtonID id)
{
if (id == OIS::MB_Left)
{
CEGUI::MouseCursor *mouse = CEGUI::MouseCursor::getSingletonPtr();
mStart.x = mouse->getPosition().d_x / (float)arg.state.width;
mStart.y = mouse->getPosition().d_y / (float)arg.state.height;
mStop = mStart;
mSelecting = true;
mRect->clear();
mRect->setVisible(true);
}
return true;
}
bool mouseReleased(const OIS::MouseEvent &arg, OIS::MouseButtonID id)
{
if (id == OIS::MB_Left)
{
performSelection(mStart, mStop);
mSelecting = false;
mRect->setVisible(false);
}
return true;
}
void performSelection(const Vector2 &first, const Vector2 &second)
{
float left = first.x, right = second.x,
top = first.y, bottom = second.y;
if (left > right)
swap(left, right);
if (top > bottom)
swap(top, bottom);
if ((right - left) * (bottom - top) < 0.0001)
return;
Ray topLeft = mCamera->getCameraToViewportRay(left, top);
Ray topRight = mCamera->getCameraToViewportRay(right, top);
Ray bottomLeft = mCamera->getCameraToViewportRay(left, bottom);
Ray bottomRight = mCamera->getCameraToViewportRay(right, bottom);
PlaneBoundedVolume vol;
vol.planes.push_back(Plane(topLeft.getPoint(3), topRight.getPoint(3), bottomRight.getPoint(3))); // 前平面
vol.planes.push_back(Plane(topLeft.getOrigin(), topLeft.getPoint(100), topRight.getPoint(100))); // 顶平面
vol.planes.push_back(Plane(topLeft.getOrigin(), bottomLeft.getPoint(100), topLeft.getPoint(100))); // 左平面
vol.planes.push_back(Plane(topLeft.getOrigin(), bottomRight.getPoint(100), bottomLeft.getPoint(100))); // 底平面
vol.planes.push_back(Plane(topLeft.getOrigin(), topRight.getPoint(100), bottomRight.getPoint(100))); // 右平面
PlaneBoundedVolumeList volList;
volList.push_back(vol);
mVolQuery->setVolumes(volList);
SceneQueryResult result = mVolQuery->execute();
deselectObjects();
SceneQueryResultMovableList::iterator itr;
for (itr = result.movables.begin(); itr != result.movables.end(); ++itr)
selectObject(*itr);
}
void deselectObjects()
{
std::list<MovableObject*>::iterator itr;
for (itr = mSelected.begin(); itr != mSelected.end(); ++itr)
(*itr)->getParentSceneNode()->showBoundingBox(false);
}
void selectObject(MovableObject *obj)
{
obj->getParentSceneNode()->showBoundingBox(true);
mSelected.push_back(obj);
}
private:
Vector2 mStart, mStop;
SceneManager *mSceneMgr;
PlaneBoundedVolumeListSceneQuery *mVolQuery;
std::list<MovableObject*> mSelected;
SelectionRectangle *mRect;
bool mSelecting;
static void swap(float &x, float &y)
{
float tmp = x;
x = y;
y = tmp;
}
};
class DemoApplication : public ExampleApplication
{
public:
DemoApplication()
: mRenderer(0), mSystem(0)
{
}
~DemoApplication()
{
if (mSystem)
delete mSystem;
if (mRenderer)
delete mRenderer;
}
protected:
CEGUI::OgreCEGUIRenderer *mRenderer;
CEGUI::System *mSystem;
void createScene(void)
{
mRenderer = new CEGUI::OgreCEGUIRenderer(mWindow, Ogre::RENDER_QUEUE_OVERLAY, false, 3000, mSceneMgr);
mSystem = new CEGUI::System(mRenderer);
CEGUI::SchemeManager::getSingleton().loadScheme((CEGUI::utf8*)"TaharezLookSkin.scheme");
CEGUI::MouseCursor::getSingleton().setImage((CEGUI::utf8*)"TaharezLook", (CEGUI::utf8*)"MouseArrow");
mCamera->setPosition(-60, 100, -60);
mCamera->lookAt(60, 0, 60);
mSceneMgr->setAmbientLight(ColourValue::White);
for (int i = 0; i < 10; ++i)
for (int j = 0; j < 10; ++j)
{
Entity *ent = mSceneMgr->createEntity("Robot" + StringConverter::toString(i + j * 10), "robot.mesh");
SceneNode *node = mSceneMgr->getRootSceneNode()->createChildSceneNode(Vector3(i * 15, 0, j * 15));
node->attachObject(ent);
node->setScale(0.1, 0.1, 0.1);
}
}
void createFrameListener(void)
{
mFrameListener = new DemoListener(mWindow, mCamera, mSceneMgr);
mFrameListener->showDebugOverlay(true);
mRoot->addFrameListener(mFrameListener);
}
};
#if 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
DemoApplication app;
try {
app.go();
} catch(Exception& e) {
#if OGRE_PLATFORM == OGRE_PLATFORM_WIN32
MessageBoxA(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;
}