OGRE中级教程四 Volume Selection and Basic Manual Objects

时间:2022-07-28 03:39:53

英语水平有限,欢迎大家批评指正OGRE中级教程四 Volume Selection and Basic Manual Objects

本文并没有将原文全部翻译,只是将其中的一些知识点翻译总结了一下,想要查看详细讲解的话,可以到原文处看一下,附上英文原文地址:http://www.ogre3d.org/tikiwiki/tiki-index.php?page=Intermediate+Tutorial+4&structure=Tutorials

ManualObjects

A Crash Course in 3D Objects

   创建网格之前我们来讨论一下什么是网格,网格是由什么组成的。一个网格粗略的由两部分组成:顶点缓存(vertex buffer)和索引缓存(index buffer)。

   顶点缓存:定义3D空间的点。每一个顶点缓存中的元素都由你可以设置的几个属性定义。你必须设置的属性是顶点的位置。除此之外,你可以设置很多可选属性,如顶点颜色、纹理坐标等等。你需要使用哪些属性是根据你用网格要干什么。

   索引缓存:作用是通过选择顶点缓存中的点来"连接他们"。索引缓存中指定每三个索引定义一个由GPU来绘制的三角形。你在索引缓存中选择的顶点的顺序告诉了图形卡,三角形是面向哪边的。一个逆时针绘制的三角形面对你,那么顺时针绘制的三角形就背对你。通常只有三角形的前面被渲染,所以确保你的三角形恰当的设置(setup)是很重要的。

   虽然所有网格都有一个顶点缓存,但不是所有网格都有一个索引缓存。如我们要创建的网格就没有索引缓存,因为我们想要创建一个空矩形。最后要注意顶点缓存和索引缓存通常都是存储在图形卡的内存中的,这样你就可以只给图形卡传一个简单、独立的命令集来告诉它,使用那些预定义的缓存一次渲染整个3D网格。

Introduction

   有两种方法使用OGRE来创建你的网格。第一种方法是吧SimpleRenderable对象划分子集并直接提供顶点缓存和索引缓存,这是最直接也是最复杂的一种创建网格的方法。要更简单的创建网格,OGRE提供了一个ManualObject的接口,它允许你使用一些简单的函数定义一个网格,而不是给缓存对象写枯燥的数据。不用把位置、颜色等写进缓存,你只需要调用positioncolour函数就行了。

Selection Box

   现在我们可以用本教程的程序来处理所有的选择盒功能,but that is just going to add clutter that doesn't need to be there. 将不同的对象独立成不同的类是一个很好的习惯,因为这让事情变得更加的整洁、简单。所有对于选择盒,我们要单独建一个SelectionBox.h头文件和一个SelectionBox.cpp文件,并声明该类为SelectionBox,它继承自Ogre::ManualObject。你的头文件和CPP文件如下:

   SelectionBox.h

#ifndef __SelectionBox_h_

#define __SelectionBox_h_

#include "OgreManualObject.h"

class SelectionBox : public Ogre::ManualObject

{

public :

       SelectionBox(const Ogre::String& name);

       ~SelectionBox(void);

       void setCorners(float left, float top, float right, float bottom);

       void setCorners(const Ogre::Vector2& topLeft, const Ogre::Vector2& topRight);

};

#endif

SelectionBox.cpp

#include "SelectionBox.h"

SelectionBox::SelectionBox(const Ogre::String& name): Ogre::ManualObject(name)

{

}

SelectionBox::~SelectionBox()

{

}

void SelectionBox::setCorners(float left, float top, float right, float bottom)

{

}

void SelectionBox::setCorners(const Ogre::Vector2& topLeft, const Ogre::Vector2& bottomRight)

{

       setCorners(topLeft.x, topLeft.y, bottomRight.x, bottomRight.y);

}

The Code

   当我们创建选择矩形时,我们让他以2D的方式渲染。我们还要确保当OGREOverlays渲染时他渲染,这样可以让他在屏幕上所有对象之上显示。这很简单,添加如下代码到SelectionBox的构造函数:

    setRenderQueueGroup(Ogre::RENDER_QUEUE_OVERLAY); // when using this, ensure   Depth Check is Off in the material         

setUseIdentityProjection(true);

setUseIdentityView(true);

setQueryFlags(0);

   第一个函数把对象的渲染队列设为Overlay队列。后面两个函数设置projectionview matricesidentityprojectionview matrices被很多渲染系统(如OpenGLDirectX)用来定义where objects go in the world.你需要知道如果设置projectionview matricesidentity,我们基本上就是要创建一个2D对象。当定义了这个对象,坐标系统也要有一点改变。我们不再使用Z轴(如果你需要Z轴,那么设为-1)。作为代替我们有一个包含XY轴范围为-11的新的坐标系。最后设对象查询标记为0,可以预防选择矩形被包含在查询结果中。

   对象已经设置好,我们现在要创建矩形。开始之前我们有个小问题,我们要根据鼠标位置调用这个函数。即我们会得到一个作为xy坐标的01之间的数,我们还需要把这些数转换成-11之间的数。更复杂的是,Y坐标还是backwards(反向的)。CEGUI中鼠标光标定义屏幕顶部为0,底部为1。在我们的新坐标系中,屏幕顶部为+1,底部为-1。幸运的是,一些快速转换会解决这个问题。添加如下代码到setCorners函数中:

   left = left * 2 - 1;

   right = right * 2 - 1;

   top = 1 - top * 2;

   bottom = 1 - bottom * 2;

   现在的位置都是在新坐标系统中的位置,下面我们要创建对象。为此我们首先调用begin方法,它需要两个参数,为对象这部分使用的材质的名字,绘制对象用的渲染操作。因为我们这里不用纹理,所以我们把材质留为空白。第二个参数是RenderOperation,我们可以使用点、线、矩形来渲染网格,如果我们要渲染一整个网格我们会使用矩形,但因为我们想要一个空矩形,所以我们用线条来渲染。线条在每个顶点和前一个你定义的顶点直接画一条线。所以创建矩形,我们定义5个点(第一个和最后一个点一样,用来连接整个矩形):

    clear();

begin("", RenderOperation::OT_LINE_STRIP);

        position(left, top, -1);

        position(right, top, -1);

        position(right, bottom, -1);

        position(left, bottom, -1);

        position(left, top, -1);

end();

   重画矩形前在最开始调用clear函数来移除之前的矩形,定义一个手动(manual)对象时,你会多次调用begin/end来创建子网格(他们有不同的materials/RenderOperations)。注意我们还要设Z参数为-1,因为我们要定义不使用Z轴的2D对象,把它设为-1将确保渲染时我们没有在摄像机的上面或后面。

   最后我们需要设置对象的绑定盒,许多场景管理器都会剔除(cull)掉不再屏幕中的对象。即使我们创建了一个2D对象,OGRE仍然是一个3D引擎并把我们的2D对象像在3D空间一样对待。这意味着如果我们创建对象并绑定到一个场景节点,当我们看向其他地方时它就会消失。为了修正这个问题,我们设对象的绑定盒为无穷大(infinite),so that the camera will always be inside of it:

setBoundingBox(AxisAlignedBox::BOX_INFINITE);

   确保我们在clear之后加上了这行代码,每次你调用ManualObject::clear,绑定盒都重设,所以如果你要创建另一个经常被删除的(clearedManualObject时,一定要注意,因为每次你重建它时绑定盒都必须被设置。

Volume Selection

Setup

   首先我们要设置(setup)一些事,第一包含SelectionBox.h IntermediateTutorial4.h中,第二在IntermediateTutorial4类的protected中声明一个指向选择盒的指针变量:

   #include SelectionBox.h

   SelectionBox* mSelectionBox;

   现在创建一个SelectionBox类实例的设置都已经设置好了,并让场景管理器为我们创建了一个volume(体积,容量)查询。添加如下代码到createScene的末尾:

   mSelectionBox = new SelectionBox("SelectionBox");

   mSceneMgr->getRootSceneNode()->createChildSceneNode()->attachObject(mSelectionBox);

   mVolQuery = mSceneMgr->createPlaneBoundedVolumeQuery(PlaneBoundedVolumeList());

   然后我们需要确保侦听器在调用过它之后进行了清除(clean up),添加如下代码到~IntermediateTutorial4

mSceneMgr->destroyQuery(mVolQuery);

if(mSelectionBox)

{

        delete mSelectionBox;

}

   注意让场景管理器清除(clean up)为我们创建的查询而不是直接删除(delete)。

Mouse Handlers

   下面我们要完成框选(volume selection),即当用户点击并拖动鼠标,屏幕上将绘制一个矩形。当松开鼠标时,所有包含在矩形中的对象都将被选中。首先处理鼠标被按下,我们需要保存开始位置并设SelectionBox为可见。添加如下代码到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;

        mSelectionBox->clear();

        mSelectionBox->setVisible(true);

}

  注意我们使用的是CEGUI::MouseCursor'XY坐标而不是OIS的。因为OIS有时得到的位置和CEGUI实际显示的位置不同。为了同步用户看到的位置,我们使用CEGUI的鼠标坐标。下面我们要做的是停止显示选择矩形并执行选择查询,当用户松开鼠标时。添加如下代码到mouseReleased函数中:

    if (id == OIS::MB_Left)

{

        performSelection(mStart, mStop);

        mSelecting = false;

        mSelectionBox->setVisible(false);

}

   最后每次鼠标移动时,我们都需要更新矩形的新坐标:

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;

            mSelectionBox->setCorners(mStart, mStop);

        }

   每当鼠标移动时我们就调整mStop向量,这样我们就可以让setCorners成员函数来使用它。

PlaneBoundedVolumeListSceneQuery

   现在SelectionBox可以正确的渲染了,我们需要执行框选了。添加如下代码到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, topbottom四个变量。If语句确保我们有最低的lefttop值。

   下面我们得查看矩形实际的大小,,如果矩形很小,我们创建一个平面(plane bound volumes)的方法就会失败。如果矩形小于屏幕大小一定百分率,我们只会返回并且不执行选择。我任意选择了0.0001作为取消查询的临界值。在实际程序中,你可能需要找到矩形中点并执行一个标准的RaySceneQuery,而不是什么都不做。

   if ((right - left) * (bottom - top) < 0.0001)

        return;

   现在我们来看函数的内容,并且我们需要它自己执行查询。PlaneBoundedVolumeQueries使用平面来围绕一个区域(enclose an area),然后选择该区域的任何对象。本例中我们将一个被5个面向内部的平面围绕的区域。为了在我们的矩形外创建这些平面,我们要创建4条射线,每条对应矩形的一个角。当我们有了这4条射线,我们就使用从射线上截取的点来创建平面:

Ogre::Ray topLeft = mCamera->getCameraToViewportRay(left, top);

Ogre::Ray topRight = mCamera->getCameraToViewportRay(right, top);

Ogre::Ray bottomLeft = mCamera->getCameraToViewportRay(left, bottom);

Ogre::Ray bottomRight = mCamera->getCameraToViewportRay(right, bottom);

   现在我们要创建平面,注意我们在射线上每100个单位任意截取一个点。也可以是2个单位而不是100个单位。唯一要注意的是前面的平面(front plane),它从Camera前的3个单位开始。

Ogre::PlaneBoundedVolume vol;

vol.planes.push_back(Ogre::Plane(topLeft.getPoint(3), topRight.getPoint(3), bottomRight.getPoint(3)));         // front plane

vol.planes.push_back(Ogre::Plane(topLeft.getOrigin(), topLeft.getPoint(100), topRight.getPoint(100)));         // top plane

vol.planes.push_back(Ogre::Plane(topLeft.getOrigin(), bottomLeft.getPoint(100), topLeft.getPoint(100)));       // left plane

vol.planes.push_back(Ogre::Plane(bottomLeft.getOrigin(), bottomRight.getPoint(100), bottomLeft.getPoint(100)));   // bottom plane

vol.planes.push_back(Ogre::Plane(topRight.getOrigin(), topRight.getPoint(100), bottomRight.getPoint(100)));     // right plane

   这些平面现在定义了一个在摄像机前扩展为无限大的"open box"。你可以把我们用鼠标画出的矩形作为在摄像机前面的"open box"的结束。现在我们创建了平面,下面要执行查询:

Ogre::PlaneBoundedVolumeList volList;

volList.push_back(vol);

mVolQuery->setVolumes(volList);

Ogre::SceneQueryResult result = mVolQuery->execute();

   最后我们需要处理查询的结果,首先取消之前被选中的对象的选定,然后选择所有被查询找到的对象。deselectObjectsselectObject函数在前一个教程中已经写好:

deselectObjects();

Ogre::SceneQueryResultMovableList::iterator iter;

for (iter = result.movables.begin(); iter != result.movables.end(); ++iter)

        selectObject(*iter);

   这就是所有我们需要为查询所做的事情。我们还可以使用volume查询的查询标记,即使本教程中我们没有用。添加如下代码到selectObjectdeselectObjects函数中:

void IntermediateTutorial4::deselectObjects()

{

       std::list<Ogre::MovableObject*>::iterator iter = mSelected.begin();

       for(iter; iter != mSelected.end(); iter++)

       {

              (*iter)->getParentSceneNode()->showBoundingBox(false);

       }

}

    void IntermediateTutorial4::selectObject(Ogre::MovableObject* obj)

{

       obj->getParentSceneNode()->showBoundingBox(true);

       mSelected.push_back(obj);

}

   编译运行你的程序,你现在可以框选场景中的对象了!