OGRE中级教程一 Animation, Walking Between Points, and Basic Quaternions

来源:互联网 发布:mac强制卸载软件 编辑:程序博客网 时间:2024/06/03 15:24

英语水平有限,欢迎大家批评指正微笑

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

Setting up the Scene

  在头文件中我们已经定义了3个变量,mEntity保存我们创建的实体,mNode保存我们创建的节点,mWalkList保存我们想让对象走到的所有点。

  首先我们要设置环境光,添加如下代码到ITutorial01::createScene函数中:

  // 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(队列)对象是一个非常高效的双端队列。我们将只使用它的一些方法,push_frontpush_back方法相应的把items放入队列最前面和尾部,frontback方法相应的返回队列最前端和尾部的值,pop_frontpop_back方法相应的移除队列最前端和末尾的items,最后empty方法返回队列是否为空。下面的代码添加了两个Vector到队列中,这是我们要机器人移动到的位置:

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

  最后设置一下camera的位置以更好的观察:

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

Animation

  我们要制作(setup)一些基本的动画,OGRE中动画很简单。为此你需要获取实体对象的AnimationState,设置它的选项并使它可用。这将激活动画,但为了运行动画,你仍需要每帧后给他添加时间。我们将一次把这些都设置好,首先找到ITutorial01::createFrameListener并在BaseApplication::createFrameListener函数之后添加如下代码:

  // Set idle animation

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

       mAnimationState->setLoop(true);

       mAnimationState->setEnabled(true);

  第二行获取了实体的AnimationState。第三行我们调用setLoop(true)使得动画一直循环,对某些动画(比如死亡时的动画),我们需要把它设为false。第四行使得动画可用。每个网格(mesh)都有他们自己的动画集。要查看你使用的某一网格的所有动画,你需要下载一个OgreMeshViewer。如果我们现在编译运行程序,没有什么改变。因为我们需要每帧更新一次动画状态(animation state)。添加如下代码到ITutorial01::frameRenderingQueued函数开始:

  mAnimationState->addTime(evt.timeSinceLastFrame);

Moving the Robot

  现在我们要完成让机器人从一个点走到另一个的的任务。我们要使用4个变量来完成机器人移动的任务。首先我们要把机器人移动的方向保存到mDirection中,把机器人移动的目的地保存到mDestination中,把机器人还要移动的距离保存到mDistance中,把机器人的移动速度保存到mWalkSpeed中。

  首先我们要设置这些变量,设置移动速度为35单位每秒。有一点需要注意,我们设置mDirection为零向量是因为我们要用它来判断机器人是否在移动。添加如下代码到ITutorial01::createFrameListener

  // Set default values for variables

        mWalkSpeed = 35.0f;

        mDirection = Ogre::Vector3::ZERO;

  下面设置机器人的动作,为了让机器人移动,我们只需要告诉他改变动画。但是,我们只想有另一个要移动到的位置时机器人才开始移动。因此我们调用ITutorial01::nextLocation函数,添加如下代码到ITutorial01::frameRenderingQueued方法的AnimationState::addTime函数之前:

  if (mDirection == Ogre::Vector3::ZERO)

       {

           if (nextLocation())

           {

               // Set walking animation

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

               mAnimationState->setLoop(true);

               mAnimationState->setEnabled(true);

           }

       }

  如果你现在编译运行程序,机器人将原地走路。这是因为机器人的起始方向为ZERO并且ITutorial01::nextLocation函数总是返回true

  现在我们要真正的在场景中移动机器人,为此我们要让机器人每帧移动一点。在ITutorial01::frameRenderingQueued函数中的AnimationState::addTime函数之前,我们上面的if语句之后添加下面的代码:

   else

        {

            Ogre::Real move = mWalkSpeed * evt.timeSinceLastFrame;

            mDistance -= move;

  Walkspeedevt.timeSinceLastFrame相乘是为了无视帧率的变化而保持速度的连贯性,如果你只写Real move = mWalkspeed,机器人在一台很慢的电脑上就会走的很慢,在一台很快的电脑上就会走的很快。

  现在我们需要查看是否要到达目标位置,即如果mDistance小于0,我们需要跳到该位置并设置下一个要移动到的位置。注意还要设置mDirectionZERO向量。如果nextLocation函数不改变mDirection,我们就不移动。

  if (mDistance <= 0.0f)

            {

                mNode->setPosition(mDestination);

                mDirection = Ogre::Vector3::ZERO;

  现在我们移动到了一个位置,然后要设置(setup)下一个位置的动作。一旦我们知道了是否要移动到另一个位置,我们就能设置合适的动画:

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

            {

                mNode->translate(mDirection * move);

            } // else

        } // if

  该做的我们基本上已经做了。ITutorial01::nextLocation函数在我们走完所有要走的位置时返回false(注意你要在该函数的末尾加上return true语句):

  if (mWalkList.empty())

            return false;

 现在我们要设置变量(还是在nextLocation函数中),从队列中获取目的地向量(destination vector),通过目的地位置减去场景节点当前位置来设置方向向量(direction vector)。现在我们遇到一个问题,还记得在frameRenderingQueued中我们让mDirection和移动量相乘吗?如果我们这样做,就需要方向向量为单位向量(即它的长为1)。normalise函数帮我们做到这些,并返回该向量原来的长度。Handy that, since we need to also set the distance to the destination

       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方向(他的默认方向)。我们要改变他的朝向,当他朝某一点移动时。

  我们要做的是获取机器人面向的方向,然后用rotate函数把他旋转到正确的位置。添加如下代码到"// Rotation Code will go here later"处:

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

        Ogre::Quaternion quat = src.getRotationTo(mDirection);

        mNode->rotate(quat);

  第一行获取了机器人面向的方向,第二行创建了一个四元数来代表(represent)从当前方向到目的方向的旋转,第三行旋转了机器人。

  我们在Basic Tutorial 4中简单的提到了四元数,但这是我们第一次实际使用它。简单的讲,四元数是对3维空间中的旋转的描述。他们时用来记录对象是如何在空间中被放置的,也可以再OGRE中用来旋转对象。第一行我们调用getOrientation函数,它返回一个描述了机器人在空间中调整方向的方法。有用OGRE不值得哪一边是机器人的"前面"们我们必须让这个orientation(方向)和UNIT_X向量相乘来得到机器人当前朝向的方向。我们把这个方面存在src变量中。第二行getRotationTo方法返回给我们一个四元数,它描述了从机器人面向的方向旋转到我们想让他面向的方向的旋转。第三行我们旋转节点让他朝向新的方向。我们的代码中只有一个问题,有一种特殊的情况SceneNode::rotate方法将失败。如果我们让机器人旋转180度,旋转代码就会出现一个0错误(zero error)而崩溃。要修正这个问题,我们需要检查是否在进行180度的旋转,如果是我们就只让机器人绕Y轴旋转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

  如果两个单位向量方向相反,那么他们的dot product就会是-1。所有如果我们dotProduct这两个向量且结果为-1,那么我们就需要绕Y轴旋转180度,否则使用旋转。为什么要加1.0f并坚持它是否小于0.0001f?不要忘了浮点数舍入误差,永远不要直接比较两个浮点数。最后,注意这两个向量的dot product的范围为[-1,1]。如果还是不太清楚,那么要做图形编程你需要至少知道基本的线性代数学。最少你需要复习一下四元数和旋转基础,并查阅一半关于基本向量和矩阵的书。

  现在代码已经完成,编译运行你的程序!