Ogre wiki 中级教程1 动画,点之间行走及四元数的基本应用

引言

在本教程中,我们将介绍如何让一个实体以动画的方式在预先定义的点之间行走。此外还将通过展示一个如何让实体朝向它所移动的方向的例子来讲解四元数的基本应用。在创作这个demo的过程中你将慢慢把代码添加到项目中,编译并观看结果。

Prerequisites (略)

Getting Started (略)

创建场景

开始之前,请注意我们在头文件定义了三个变量。mEntity将保存我们创建的实体,mNode将保存我们创建的节点,而mWalkList包含我们希望让对象行走至之上的各个点。

查找ITutorial01::createScen函数并添加以下代码。首先我们把环境光的各个分量设置为全满,使我们能看到放置在场景中的对象。

// Set the default lighting.
         mSceneMgr->setAmbientLight(Ogre::ColourValue(1.0f, 1.0f, 1.0f));

接下来我们将在屏幕上创建一个机器人从而能操纵它。为完成这个任务我们将创建机器人对应的实体,然后创建它所挂接的场景节点。

// Create the entity
        mEntity = mSceneMgr->createEntity("Robot", "robot.mesh");
 
        // Create the scene node
        mNode = mSceneMgr->getRootSceneNode()->
            createChildSceneNode("RobotNode", Ogre::Vector3(0.0f, 0.0f, 25.0f));
        mNode->attachObject(mEntity);

这些看起来可能非常简单,所以我将不解释每一处的细节。下面一段代码中,我们要告诉机器人它需要移动到哪里。如果你从未学习过STL,以下为你解释deque的概念。deque对象是双端对列的一个高效实现,我们只需使用它的少数方法。push_front和push_back方法分别在队头和队尾插入元素。front和back方法分别返回队头和队尾的元素。pop_front和pop_back方法分别从队头和队尾删除元素。最后,empty方法返回该队列是否为空。下列代码添加两个向量至deque,我们稍后将让机器人在它们之间移动。

// Create the walking list
        mWalkList.push_back(Ogre::Vector3(550.0f,  0.0f,  50.0f ));
        mWalkList.push_back(Ogre::Vector3(-100.0f,  0.0f, -200.0f));

接下来让我们在场景中放置一些对象表示机器人将要经过的点。这将让我们看到机器人相对于屏幕上的对象在移动。注意它们所在位置的负Y分量,这把对象置于机器人将经过的点的下方,当机器人到达正确地点的时候将站在上面。

// Create objects so we can see movement
        Ogre::Entity *ent;
        Ogre::SceneNode *node;
 
        ent = mSceneMgr->createEntity("Knot1", "knot.mesh");
        node = mSceneMgr->getRootSceneNode()->createChildSceneNode("Knot1Node",
            Ogre::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",
            Ogre::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",
            Ogre::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 handiwork
        mCamera->setPosition(90.0f, 280.0f, 535.0f);
        mCamera->pitch(Ogre::Degree(-30.0f));
        mCamera->yaw(Ogre::Degree(-15.0f));

现在编译并运行以上代码。

动画

我们现在要创建一些基本的动画。在Orge中实现动画非常简单。你只需从实体对象获取AnimationState,设置它的属性,然后让它可用。这将激活动画,但是为了让动画能够播放你需在各帧之后为动画添加时间参数。我们将一步完成这些工作。首先,查找ITutorial01::createFrameListener函数,并在调用BaseApplication::createFrameListener之后添加以下代码:

// Set idle animation
        mAnimationState = mEntity->getAnimationState("Idle");
        mAnimationState->setLoop(true);
        mAnimationState->setEnabled(true);

第二行从实体中获取AnimationState,第三行我们髙用setLoop( true )让动画一直循环。对于一些动画(如死亡动画),我们将其参数设为false。第四行真正让动画可用。但是等等...我们从哪里得来的"idle"?为何这个魔数会在这个地方?因为所有网格都拥有为它们所定义的动画集合。为了看到你所使用的特定网格的所有动画,你需要下载OgreMeshViewer以察看网格。

现在,如果我们编译并运行demo,我们看到...什么都没有改变。这是因为我们需要在每一帧用时间变量更新动画状态。查找Tutorial01::frameRenderingQueued方法并添加这行代码于函数的开始处:

mAnimationState->addTime(evt.timeSinceLastFrame);

现在编译并运行程序,你将看到机器人在原地表现空闲状态的动画。

移动机器人

现在我们开始着手来完成让机器人在点和点之间移动这个棘手的任务。开始之前我要解释一下我们定义好的变量。我们用4个变量来完成移动机器人的任务。首先我们用mDirection存储机器人移动的方向,用mDestination存储机器人将要到达的当前目标位置,用mDistance存储机器人接下来要移动的距离。最后用mWalkSpeed存储机器人的移动速度。


我们要做的第一件事是设置这些变量。我们将设置移动速度为每秒35个单位。有件重要的事情需要要注意:我们明确地设置mDirection为零向量,因为稍后我们将用它来确定机器人移动与否。添加以下代码至ITutorial01::createFrameListener:

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


完成这些后,我们需要让机器人运动起来。我们简单地改变动画让机器人移动。我们只想在有另一个可移动至之上的地点时才移动机器人。基于这个原因我们调用了ITutorial01::nextLocation function方法。在ITutorial01::frameRenderingQueued方法之上,调用AnimationState::addTime之前添加这段代码:

if (mDirection == Ogre::Vector3::ZERO)
        {
            if (nextLocation())
            {
                // Set walking animation
                mAnimationState = mEntity->getAnimationState("Walk");
                mAnimationState->setLoop(true);
                mAnimationState->setEnabled(true);
            }
        }

如果你编译并运行目前的代码,这个机器人将在原地走动。这是因为机器人从值为零向量的方向开始并且ITutorial01::nextLocation始终返回true。下面的步骤我们将在ITutorial01::nextLocation方法中多添加一点智能。

现在我们要真正开始在场景中移动机器人,为此我们需要让它在每帧移动一点,找到ITutorial01::frameRenderingQueued方法,我们将在前面的if语言之后AnimationState::addTime调用之前添加下列代码。这段代码将处理机器人真正开始移动的情况;mDirection != Ogre::Vector3::ZERO
mWalkspeed乘以evt.timeSinceLastFrame的原因是为了保持走动速度恒定,即使帧率是变化的。


如果你只写上Real move = mWalkspeed,机器人在速度较慢的机子上将会走得较慢,反之则走得较快。

else
         {
             Ogre::Real move = mWalkSpeed * evt.timeSinceLastFrame;
             mDistance -= move;

现在,我们需要检测我们是否走过了目标点。即如果现在mDistance小于0,我们需要跳回到那个点上,并设置移动到下一个点。请注意我们把mDirection设为零向量,如果nextLocation 方法没有改变mDirection(即不需要再走向任何点)则我们不再需要来回走动。

if (mDistance <= 0.0f)
             {
                 mNode->setPosition(mDestination);
                 mDirection = Ogre::Vector3::ZERO;

我们已经移动到一个点,现在需要设置到下一个点的运动。一旦我们知道是否需要移动到下一个点,我们就可以设置适当的动画。如果正朝一个点走动则设置走动动画,如果没有目标点则设置空闲动画。走完各个点设置空闲动画是一件简单的事情:

// 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
                }
            }


注意如果队列里还有需要行走至其上的点我们就不再需要再次设置走动动画。因为机器人已经在走动了,没有理由让它再做一次。然而,如果机器人需要走向另一个点则我们需要旋转它面向该点。现在我们在else分支留下一个站位注释,记住稍后我们将回到这一处。

以上处理了当我们离目标地点非常近的情况。现在我们需要处理一般情况,即当我们在走向该点的路上但还没在那里。为完成这个任务我们在走动的方向上平移机器人,以move变量计算的单位数移动它,通过添加以下代码来实现:


             {
                 mNode->translate(mDirection * move);
             } // else
         } // if

差不多完成了。我们的代码做了所有的事情除了设置移动所需的变量。如果我们能正确设置移动变量我们的机器人将像设想的一样移动。查找ITutorial01::nextLocation函数,这个函数当走完所有点的时候返回false。以下代码将是这个函数的第一行,(注意你必须在函数的结尾处返回true)

if (mWalkList.empty())
             return false;

现在我们需要设置变量(仍然在nextLocation method方法)。首先我们将目标向量移出队列,我们将通过目标向量与场景节点的当前位置相减来得到方向向量,但是这有一个问题。记起来我们在 frameRenderingQueued方法里将mDirection乘以移动的总量吗?如果我们这么做,就需要方向向量是一个单位向量(即它的长度等于1)。这个规范化函数为我们做了这些,并返回向量原来的长度。这很方便,因为我们还需要设置到目的地的距离。

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();

现在编译并运行以上代码,它能工作了!可以这样说。机器人现在朝各个点行走,但是它一直面向Ogre::Vector3::UNIT_X方向(它的默认方向)。当它朝着目标点移动的时候我们将改变它所面向的方向。

我们需要做的是获取机器人的朝向,然后使用旋转函数将对象旋转至正确的位置。插入以下代码于我们之前留下的站位注释。第一行获取机器人面向的方向,第二行创建一个四元数表示从当前方向至目标方向的旋转。第三行真正旋转了机器人。

Ogre::Vector3 src = mNode->getOrientation() * Ogre::Vector3::UNIT_X;
         Ogre::Quaternion quat = src.getRotationTo(mDirection);
         mNode->rotate(quat);

我们在基本教程4简单提及了四元数,但这是第一次使用。简单说来,四元数表示在3D空间里的旋转。它们被用来跟踪对象如何在空间中定位,且可以用于在Ogre中旋转对象。第一行我们调用getOrientation方法,它返回一个表示机器人在空间中的朝向的四元数,因为Orge无法确定哪一面才是机器人的正面,我们必须将朝向乘以UNIT_X向量(这是机器人"天生"的朝向)来得到机器人的当前朝向。我们将这个方向存储于src变量。第二行getRotationTo 方法返回一个四元数表示从机器人朝向的方向至我们希望它面向的方向的的旋转。第三行我们旋转节点让它面向一个新的方向。

我们编写的所有代码中只有一个问题,有一种情况会使SceneNode::rotate方法失败,如果我们尝试让机器人做180度旋转,执行旋转的代码将会因为除零错误而崩溃。为了修正它,我们需要测试是否执行了180旋转。如果是这样,我们将简单地让机器人偏航180度以替代旋转。为此我们删除我们刚放进去的三行代码并以下列代码替代:

Ogre::Vector3 src = mNode->getOrientation() * Ogre::Vector3::UNIT_X;
        if ((1.0f + src.dotProduct(mDirection)) < 0.0001f)
        {
            mNode->yaw(Ogre::Degree(180));
        }
        else
        {
            Ogre::Quaternion quat = src.getRotationTo(mDirection);
            mNode->rotate(quat);
        } // else

这些代码除了if语句包含的东西以外都是不言自明的。如果两个单位向量彼此相反(即它们之间的夹角为180度),则它们的点积将为-1。所以如果我们求出两个向量的点积并测试其结果等于-1,则需要偏航180度,除此之外我们用旋转来替代。为什么我加上1.0f并测试是否小于0.0001f?别忘了浮点舍入误差。你永远不要直接比较两个浮点数。最后,你至少需要懂一点图形程序所需要的线性代数知识!至少,你应该复习一下四元数与旋转入门教程,并阅读一些关于基本的向量和距阵运算的书籍。

现在我们的代码写好了,编译并运行这个demo可以看到机器人在给定的各个点之间走动。

转载于:https://www.cnblogs.com/quasimodo/archive/2012/08/24/2653762.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值