经典的OGRE机器人的例子(通过例子总结,包含四元数和动画等用法)

介绍

这个教程包含了如何使用一个Entity,移动它,并让它在预先定义的点间移动,也包含了四元数旋转的基础

通过如何让一个实体朝着他移动的方向。 你可以慢慢增加代码到工程并观看生成后的结果。

 

开始

首先,给这个demo创建一个新工程, 并增加代码到源文件。

 

#include "ExampleApplication.h"

 

#include <deque>

using namespace std;

 

class MoveDemoListener : public ExampleFrameListener

{

public:

 

    MoveDemoListener(RenderWindow* win, Camera* cam, SceneNode *sn,

    Entity *ent, deque<Vector3> &walk)

    : ExampleFrameListener(win, cam, false, false), mNode(sn),

                            mEntity(ent), mWalkList( walk )

    {

    } // MoveDemoListener

 

    /* This function is called to start the object moving to the next position

    in mWalkList.

    */

    bool nextLocation( )

    {

        return true;

    } // nextLocation( )

 

    bool frameStarted(const FrameEvent &evt)

    {

        return ExampleFrameListener::frameStarted(evt);

    }

protected:

    Real mDistance; // The distance the object has left to travel

    Vector3 mDirection; // The direction the object is moving

    Vector3 mDestination; // The destination the object is moving towards

 

    AnimationState *mAnimationState; // The current animation state of the object

 

    Entity *mEntity; // The Entity we are animating

    SceneNode *mNode; // The SceneNode that the Entity is attached to

    std::deque<Vector3> mWalkList; // The list of points we are walking to

 

    Real mWalkSpeed; // The speed at which the object is moving

};

 

 

class MoveDemoApplication : public ExampleApplication

{

protected:

public:

    MoveDemoApplication()

    {

    }

 

    ~MoveDemoApplication()

    {

    }

protected:

    Entity *mEntity; // The entity of the object we are animating

    SceneNode *mNode; // The SceneNode of the object we are moving

    std::deque<Vector3> mWalkList; // A deque containing the waypoints

 

    void createScene(void)

    {

    }

 

    void createFrameListener(void)

    {

        mFrameListener= new MoveDemoListener(mWindow, mCamera,

                        mNode, mEntity, mWalkList);

        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

    MoveDemoApplication app;

 

    try {

        app.go();

    } catch( Exception& e ) {

#if OGRE_PLATFORM == OGRE_PLATFORM_WIN32

        MessageBox( NULL, e.getFullDescription().c_str(), "An exception has occured!",

        MB_OK | MB_ICONERROR | MB_TASKMODAL);

#else

        fprintf(stderr, "An exception has occured: %s\n",

        e.getFullDescription().c_str());

#endif

    }

 

return 0;

}

 

对代码的分析: 

设置这个场景

在开始前,注意我们已经在MoveDemoApplication里定义了三个变量。mEntity保存了我们创建的entity。mNode 保存了我们创建的节点 。mWalkList 包含了我们希望移动到的所有点。

接下来到 MoveDemoApplication::createScene 函数里并增加下面的代码, 我们先要设置环境光来让我们在屏幕上看到场景里的所有对象:

     // Set the default lighting.

    mSceneMgr->setAmbientLight( ColourValue( 1.0f 1.0f 1.0f ) );

接下来,我们创建一个机器人到屏幕,好让我们可以播放它。那你就需要创建一个机器人实体,然后创建一个SceneNode让它依附:

    // Create the entity

    mEntity = mSceneMgr->createEntity( "Robot" "robot.mesh" );

    // Create the scene node

    mNode = mSceneMgr->getRootSceneNode( )->

    createChildSceneNode( "RobotNode" Vector3( 0.0f 0.0f 25.0f ) );

    mNode->attachObject( mEntity );

这些都是非常基础的,因此我不再相继解释。在下个代码块,我们要要告诉机器人需要移动到哪里。

deque 对象是一个很高效的double ended queue .我们只使用他的一些方法。 push_front 和 push_back 方法把 项目 分别队列最前或结尾放入。front 和 back 方法返回queue的最前和结尾的值  pop_front 和 pop_back 为移除。 下面的代码我们存放两个向量, 为后面我们让机器人移动的:

    // Create the walking list

    mWalkList.push_back( Vector3( 550.0f 0.0f 50.0f ) );

    mWalkList.push_back( Vector3(-100.0f 0.0f -200.0f ) );

下面,我们需要放置一些对象到场景,来显示机器人在向目标移动。这样我们就能在屏幕上看到机器人相对于其他物体移动。 注意他们位置的 -Y值 ,把对象放在机器人要移动位置的下面,这样当他到达正确位置时,就会站在那个对象的上面:

     // Create objects so we can see movement

    Entity *ent;

    SceneNode *node;

    ent = mSceneMgr->createEntity( "Knot1" "knot.mesh" );

    node = mSceneMgr->getRootSceneNode( )->createChildSceneNode( "Knot1Node"

    Vector3( 0.0f -10.0f 25.0f ) );

    node->attachObject( ent );

    node->setScale( 0.1f 0.1f 0.1f );

    ent = mSceneMgr->createEntity( "Knot2" "knot.mesh" );

    node = mSceneMgr->getRootSceneNode( )->createChildSceneNode( "Knot2Node"

    Vector3( 550.0f -10.0f 50.0f ) );

    node->attachObject( ent );

    node->setScale( 0.1f 0.1f 0.1f );

    ent = mSceneMgr->createEntity( "Knot3" "knot.mesh" );

    node = mSceneMgr->getRootSceneNode( )->createChildSceneNode( "Knot3Node"

    Vector3(-100.0f -10.0f-200.0f ) );

    node->attachObject( ent );

    node->setScale( 0.1f 0.1f 0.1f );

最后,我们希望摄象机在一个好的视点观看。我们移动摄象机到一个好的位置:

     // Set the camera to look at our handywork

    mCamera->setPosition( 90.0f 280.0f 535.0f );

    mCamera->pitch( Degree(-30.0f) );

    mCamera->yaw( Degree(-15.0f) );

现在编译和运行,你应该看到这样:

 经典的OGRE机器人的例子(通过例子总结,包含四元数和动画等用法)_第1张图片

在进入下一节前, MoveDemoListener的构造函数需要注释一下, 作为在MoveDemoApplication::createFrameListener里第一行调用方法, 我们通过构造参数传递SceneNode ,Entity和deque三个参数 。

 

动画

我们需要设置一些基本的动画。 动画在引擎里是非常简单的。 你需要从实体对象获得AnimationState 设置它的选项,并enable 它。 这就能让动画动起来,但你需要在每后增加time来让动画运行起来。

首先到 MoveDemoListener的构造函数,增加下面的代码:

    // Set idle animation

    mAnimationState = ent->getAnimationState( "Idle" );

    mAnimationState->setLoop( true );

    mAnimationState->setEnabled( true );

第2行是获得Entity的AnimationState ,第3行调用setLoop(true),可以让动画一遍一遍的循环。有些动画(比如死亡),你可能需要把它设置为false. 第4行实际上允许动画。

 显然,如果我们编译和运行,我们会看到没有任何改变。这是因为我们需要在每帧开始都更新动画状态。

找到 MoveDemoListener::frameStarted 方法 并增加这行代码到函数里:

    mAnimationState->addTime( evt.timeSinceLastFrame );

现在运行程序,你可以看到机器人执行他的IDLE状态站在空间里。

 

移动机器人

现在我们要执行让机器人从一个点走到另一个点的任务了。在开始前,我要描述一下保存在MoveDemoListener

类里的变量。我们使用4个变量来完成机器人移动的任务。首先,我们要保存机器人移动的朝向mDirection

我们要保存机器人移动的目的点mDestination, 机器人移动的剩余距离mDistance, 机器人的移动速度

mWalkSpeed

 

我们首先应该清除 MoveDemoListener 的构造函数, 用稍微不同的代码来替代以前的。 首先,需要设置类的

变量。我们设置行走速度为35单位每秒。这个是需要注意的。我们设置mDirection 为0向量,因为梢后我们

要使用它来判断我们是否移动机器人。

    // Set default values for variables

    mWalkSpeed = 35.0f;

    mDirection = Vector3::ZERO;

 

现在我们需要设置机器人运动。如果有一个其他的位置,我们希望机器人开始移动。 因此,我们调用

nextLocation 函数。 把这个代码增加在 MoveDemoListener::frameStarted 方法里的最上面。

    if ( mDirection == Vector3::ZERO )

    {

        if ( nextLocation() )

        {   

            // Set walking animation

            mAnimationState = mEntity->getAnimationState( "Walk" );

            mAnimationState->setLoop( true );

            mAnimationState->setEnabled( true );

        }

    }

 

如果你现在编译和运行代码正确,机器人将在空间里行走。 因为机器人是从ZERO方向开始行走,而

MoveDemoListener::nextLocation 的函数返回的一直是true. 下一步我们要增加一些智能

在MoveDemoListener::nextLocation 函数里

 

现在我们将实际的在场景里移动机器人。 为了实现它,我们需要他在每帧移动一些。

到MoveDemoListener::frameStarted方法里,我们在上次增加片段的后面增加下面的代码段

这个代码在 mDirection != Vector3::ZERO让机器人实际移动。

    else

    {

        Real move = mWalkSpeed * evt.timeSinceLastFrame;

        mDistance -= move;

 

现在,我们需要检测我们的移动是否超过了目标位置。那就是,如果mDistance小于0,我们将

跳出这个目标点,并设置下一个点。注意我们将把mDirection 设置为ZERO 向量。如果

nextLocation 不在改变mDirection ,我们也就不再移动了。

    if (mDistance <= 0.0f)

    {

        mNode->setPosition( mDestination );

        mDirection = Vector3::ZERO;

 

现在我们已经移动到这个点,我们需要设置下一个点的运动,当我们知道是否需要移动到下一个点时,我们可以

设置合适的动画。如果要去另外一个点设为 walking , 如果没有了就设置为 idle.

     // Set animation based on if the robot has another point to walk to.

    if (! nextLocation( ) )

    {

        // Set Idle animation

        mAnimationState = mEntity->getAnimationState( "Idle" );

        mAnimationState->setLoop( true );

        mAnimationState->setEnabled( true );

    }

    else

    {

        // Rotation Code will go here later

    }

}

   

注意我们不需要再设置 walking 动画,如果还有点在 queue 里要移动。因为机器人已经是 walking

状态,没理由让他再次去做。然而,如果机器人需要移动到另外一个点,我们需要旋转他面对那个点。

因为一些其他原因,我们现在先放置注释符号。我们稍后会回来处理

 

当我们非常接近目标点时,就要注意了。我们当处理一个正常情况,当机器人正在前往位置点而还没到时。

我们需要平移机器人在我们移动的方向的,并通过移动变量移动它一定的距离:

     else

    {

        mNode->translate( mDirection * move );

    } // else

} // if

 

我们差不多完成了,我们的代码除了设置移动变量外,可以做所有的事了。如果我们能设置一个合理的

移动变量,我们的机器人会象我们希望的那样移动。找到 MoveDemoListener::nextLocation 函数

这个函数返回false,当我们已经走完所的点时。这将是我们函数一开始就要做的。

    if ( mWalkList.empty() )

        return false;

 

现在我们要设置移动变量,首先我们从队列里获得目标响亮,我们通过目标点减当前场景节点的方式获得

方向向量。而且我们需要方向向量为一个单位向量(长度等于1),normalise 为我们实现了这些。并返回了

原来向量的长度。 因为我们需要设置到目标的距离。

    mDestination = mWalkList.front( ); // this gets the front of the deque

    mWalkList.pop_front( ); // this removes the front of the deque

   

    mDirection = mDestination - mNode->getPosition( );

    mDistance = mDirection.normalise( );

 

现在编译和运行代码,他应该可以工作了。 机器人现在可以移动到所有的点,但他还是一直朝向

Vector3::UNIT_X 方向(默认的)  当移动到下一个点时,我们需要改变他的方向

 

我们需要做的是获得机器人的朝向,然后用rotate函数来旋转对象到正确的位置。把下面的代码放到前面

注释的地方。 第一行获得机器人的朝向,第2行建立一个四元数表现从当前方向到目标向量的旋转。

第3行是实际旋转机器人。

     //UNIT_X是机器人的默认正面朝向,即面向正X轴
   // mNode是挂接robot的结点
   // src运算的结果就是,下一帧机器人的应该面对的方向.(是一个3维向量)

    Vector3 src = mNode->getOrientation( ) * Vector3::UNIT_X;  【getOrientation函数返回一个四元数,一个四元数乘以一个向量,结果就是把这个向量进行对应旋转,得到旋转后的新向量。mNode->getOrientation( ) 返回的是这个节点从最开始到现在进行过的所有旋转操作,当然这些旋转操作包装在一个四元数对象中。然后用这个四元数乘以节点最初的方向就得到当前节点的朝向。】

    Ogre::Quaternion quat = src.getRotationTo( mDirection );  【即根据两个方向向量获取从其中一个向量(也叫源向量)转到另一个向量(也叫目的向量),对应的四元数】

    mNode->rotate( quat );  【对节点进行旋转,而且旋转的细节又一次存入到mNode具有的四元数对象中了,以后从mNode->getOrientation( )可以获取这个四元数】

 【注意,在Ogre中,旋转是使用四元数来储存的,可以认为四元数是一堆旋转操作的包装,把这个包装作用在哪个节点头上,哪个节点就要运行这一堆旋转操作】

【关于四元数入门参见本博客  四元数入门(简单容易理解)

还有一些问题。四元数是什么? 基本上说,四元数表现为一个三维空间的旋转。它们用来追踪对象是如何在空间被

放置的。 第一行我们调用 getOrientation 方法,会返回一个四元数来表现机器人当前朝向,通过于UNIT_X向量

相乘, 我们获得当前机器人的朝向。我们保存在src中。第2行 getRotationTo 提供给我们一个四元数来表现

机器人从当前朝向转到我们需要的朝向的旋转。在第3行里,我们旋转节点面向新的方向。

 

我们上面创建的代码还有点问题。有一种情况下SceneNode::rotate 会失败。如果我们试图旋转189度,这个旋转

代码会因为被ZERO除而失败。为了修正他,我们需要测试是否执行180度旋转,如果是,我们将对机器人做简单的

180度yaw来代替旋转。因此,删除上面三行,用下面的的来覆盖:

 

    Vector3 src = mNode->getOrientation( ) * Vector3::UNIT_X;

    if ( (1.0f + src.dotProduct( mDirection )) < 0.0001f )  【dotProduct是计算两个向量的点积的意思,点两个单位向量的点积的结果不能等于或接近-1】

    {

        mNode->yaw( Degree(180) );

    }

    else

    {

        Quaternion quat = src.getRotationTo( mDirection );

        mNode->rotate( quat );

    } // else

 

如果两个向量是彼此相对的(就是说,他们之间角度是180度)那么他们的点乘为 -1.如果我们点乘两个向量的

结果等于 -1,我们需要yaw 180度,否则就用rotate 来代替。 为了是小于0.0001f? 不要忘记浮点的取舍

错误。你不能直接比较两个浮点值。 最后要注意的,在这个例子里,两个向量点乘的值是在[-1, 1]之间的。

你需要一些基本的线性代数知识来实现图形程序。

 

我们的代码完成了,编译和运行Demo,来看看机器人沿我们所给的点移动吧!

 

 

所有的完整代码在下面,把下面的代码考到一个cpp中就可以直接运行了:

#include "ExampleApplication.h"
#include <deque>

#pragma comment(lib,"OgreMain_d.lib")
#pragma comment(lib,"OIS_d.lib")

using namespace std;

class MoveDemoListener : public ExampleFrameListener
{
public:
 MoveDemoListener(RenderWindow* win, Camera* cam, SceneNode *sn,
  Entity *ent, std::deque<Vector3> &walk)
  : ExampleFrameListener(win, cam, false, false), mNode(sn),
  mEntity(ent), mWalkList(walk)
 {
  //设置不走动时机器人的动画
  mAnimationState = ent->getAnimationState("Idle");
  mAnimationState->setLoop(true);
  mAnimationState->setEnabled(true);

  // Set default values for variables
  mWalkSpeed = 35.0f;
  mDirection = Vector3::ZERO;


 } // MoveDemoListener


 /* This function is called to start the object moving to the next position
 in mWalkList.
 */
 bool nextLocation()
 {
  if (mWalkList.empty())
   return false;

  mDestination = mWalkList.front(); // this gets the front of the deque
  mWalkList.pop_front(); // this removes the front of the deque
  mDirection = mDestination - mNode->getPosition();
  mDistance = mDirection.normalise();

  return true;
 } // nextLocation()

 bool frameStarted(const FrameEvent &evt)
 {
  if (mDirection == Vector3::ZERO)
  {
   if (nextLocation())
   {   
    //设置行走状态的动画
    mAnimationState = mEntity->getAnimationState("Walk");
    mAnimationState->setLoop(true);
    mAnimationState->setEnabled(true);
   }
  }
  else
  {
   Real move = mWalkSpeed * evt.timeSinceLastFrame;
   mDistance -= move;
   if (mDistance <= 0.0f)
   {
    mNode->setPosition(mDestination);
    mDirection = Vector3::ZERO;
    //根据机器人下一步是否还有要到达的位置决定要用的动画状态
    if (!nextLocation())
    {
     // Set Idle animation
     mAnimationState = mEntity->getAnimationState("Idle");
     mAnimationState->setLoop(true);
     mAnimationState->setEnabled(true);
    }
    else
    {
     //机器人面朝向旋转的代码
     Vector3 src = mNode->getOrientation() * Vector3::UNIT_X;
     if ((1.0f + src.dotProduct(mDirection)) < 0.0001f)
     {
      mNode->yaw(Degree(180));
     }
     else
     {
      Quaternion quat = src.getRotationTo(mDirection);
      mNode->rotate(quat);
     } // else
    }
   }
   else
   {
    mNode->translate(mDirection * move);
   } // else
  } // if
  
  //动画状态每一帧更新以便把动画显示出来(传一个时间间隔作为参数)
  mAnimationState->addTime(evt.timeSinceLastFrame);

  return ExampleFrameListener::frameStarted(evt);
 }

protected:
 Real mDistance; // The distance the object has left to travel
 Vector3 mDirection; // The direction the object is moving
 Vector3 mDestination; // The destination the object is moving towards
 AnimationState *mAnimationState; // The current animation state of the object
 Entity *mEntity; // The Entity we are animating
 SceneNode *mNode; // The SceneNode that the Entity is attached to
 std::deque<Vector3> mWalkList; // The list of points we are walking to
 Real mWalkSpeed; // The speed at which the object is moving
};

 

 

class MoveDemoApplication : public ExampleApplication

{
protected:
public:
 MoveDemoApplication()
 {

 }

 ~MoveDemoApplication()
 {

 }

protected:
 Entity *mEntity; // The entity of the object we are animating
 SceneNode *mNode; // The SceneNode of the object we are moving
 std::deque<Vector3> mWalkList; // A deque containing the waypoints

 void createScene(void)
 {
  //设置环境光,即全局光照
  mSceneMgr->setAmbientLight(ColourValue(1.0f, 1.0f, 1.0f));
  //建一个机器人实体
  mEntity = mSceneMgr->createEntity("Robot", "robot.mesh");
  //建一个场景节点
  mNode = mSceneMgr->getRootSceneNode()->
   createChildSceneNode("RobotNode", Vector3(0.0f, 0.0f, 25.0f));
  mNode->attachObject(mEntity);
  //建一个机器人行走路径点集的列表
  mWalkList.push_back(Vector3(550.0f, 0.0f, 50.0f));
  mWalkList.push_back(Vector3(-100.0f, 0.0f, -200.0f));

  //建一些其他实体,作为机器人移动过程中的参照物
  Entity *ent;
  SceneNode *node;
  ent = mSceneMgr->createEntity("Knot1", "knot.mesh");
  node = mSceneMgr->getRootSceneNode()->createChildSceneNode("Knot1Node",
   Vector3(0.0f, -10.0f, 25.0f));
  node->attachObject(ent);
  node->setScale(0.1f, 0.1f, 0.1f);
  ent = mSceneMgr->createEntity("Knot2", "knot.mesh");
  node = mSceneMgr->getRootSceneNode()->createChildSceneNode("Knot2Node",
   Vector3(550.0f, -10.0f, 50.0f));
  node->attachObject(ent);
  node->setScale(0.1f, 0.1f, 0.1f);
  ent = mSceneMgr->createEntity("Knot3", "knot.mesh");
  node = mSceneMgr->getRootSceneNode()->createChildSceneNode("Knot3Node",
   Vector3(-100.0f, -10.0f,-200.0f));
  node->attachObject(ent);
  node->setScale(0.1f, 0.1f, 0.1f);

  //调整下摄像机以便于观看
  mCamera->setPosition(90.0f, 280.0f, 535.0f);
  mCamera->pitch(Degree(-30.0f));
  mCamera->yaw(Degree(-15.0f));
 }

 

 void createFrameListener(void)
 {
  mFrameListener= new MoveDemoListener(mWindow, mCamera,

   mNode, mEntity, mWalkList);

  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

 MoveDemoApplication app;

 

 try {

  app.go();

 } catch(Exception& e) {

#if OGRE_PLATFORM == OGRE_PLATFORM_WIN32

  MessageBox(NULL, e.getFullDescription().c_str(), "An exception has occured!",

   MB_OK | MB_ICONERROR | MB_TASKMODAL);

#else

  fprintf(stderr, "An exception has occured: %s\n",

   e.getFullDescription().c_str());

#endif

 }

 

 return 0;

}

 

 

 

你可能感兴趣的:(经典的OGRE机器人的例子(通过例子总结,包含四元数和动画等用法))