引言

在本教程中,我们将介绍如何让一个实体以动画的方式在预先定义的点之间行走。此外还将通过展示一个如何让实体朝向它所移动的方向的例子来讲解四元数的基本应用。在创作这个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

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

  1. 【学习笔记】Unreal Engine 4 虚幻引擎蓝图中级教程物理碰撞教程

    教程链接:Unreal Engine 4 虚幻引擎蓝图中级教程物理碰撞 想共享的小伙伴可以E-mail我:lxbcolorgalaxy@qq.com 目录 第一章 常见的物理现象及分析 1蓝图模板中的 ...

  2. 机器学习中级教程 7.数据泄漏

    机器学习中级教程 1.介绍 2.缺失值 3.分类变量 4.管道(Pipelines) 5.交叉验证 6.梯度提升(XGBoost) 7.数据泄漏 在本教程中,您将了解什么是数据泄漏以及如何防止它.如果 ...

  3. 【ROS wiki】ros wiki官方教程与ROS wiki页面检索

    ROS wiki系列文章简介:ROS wiki系列文章是本人ROS专栏下的子专题.该系列文章主要用来介绍:ROS初学者如何利用好ROS官方提供的ROS wiki平台,来查询ROS资料,了解ROS包的功 ...

  4. XamarinAndroid组件教程设置动画的设置插值器

    XamarinAndroid组件教程设置动画的设置插值器 为动画设置插值器,可以使用BaseItemAnimator抽象类中的SetInterpolator()方法,其语法形式如下: public v ...

  5. XamarinAndroid组件教程设置动画的时长参数

    XamarinAndroid组件教程设置动画的时长参数 在添加动画的时候,开发者还可以动画参数进行设置,如动画持续的时长.插值器等.下面依次讲解动画参数的设置方法. 1.设置动画时长 设置动画持续的时 ...

  6. XamarinAndroid组件教程RecylerView动画组件使用动画(3)

    XamarinAndroid组件教程RecylerView动画组件使用动画(3) (8)打开Main.axml文件,构建主界面.代码如下: <?xml version="1.0&quo ...

  7. XamarinAndroid组件教程RecylerView动画组件使用动画(2)

    XamarinAndroid组件教程RecylerView动画组件使用动画(2) 如果开发者要为RecylerView的子元素添加动画效果,需要使用RecyclerView类中的SetItemAnim ...

  8. Xamarin Android组件篇教程RecylerView动画组件RecylerViewAnimators(1)

    Xamarin Android组件篇教程RecylerView动画组件RecylerViewAnimators(1) RecyclerView是比ListView和GridView更为强大的布局视图, ...

  9. 5、CC2541芯片中级教程-OSAL操作系统(PWM+看门狗)

    本文根据一周CC2541笔记汇总得来-- 适合概览和知识快速索引-- 全部链接: 中级教程-OSAL操作系统\OSAL操作系统-实验01 OSAL初探 [插入]SourceInsight-工程建立方法 ...

最新文章

  1. JQuery实现倒计划按钮
  2. VMware三种网络模式根本区别(图)
  3. Kotlin 中的 run、let、with、apply、also、takeIf、takeUnless 语法糖使用和原理分析
  4. 使用css3制作正方形、三角形、扇形和饼状图
  5. python 使用dir() help() 查看一个对象所有拥有的方法和属性
  6. 1000多首无损歌曲合集
  7. c语言oj合法标识符,YTUOJ-C语言合法标识符
  8. 18个C/C++的基本知识点,带好小本子记录一下
  9. D3 treecluster
  10. LeetCode—2.堆排序算法
  11. [2018.03.14 T3] 圈圈(cyclic)
  12. 高级火山图 突出显示自定义的基因volcanic volcano plot duqiang 差异基因火山图
  13. 现代化智能一体化机柜
  14. 5种2D Attention整理(Non-Local、Criss-Cross、SE、CBAM、Dual-Attention)
  15. 80004005 mysql_数据库报错80004005
  16. 在 Pixel 3 手机上学习预测深度
  17. Golang 的 mgo 连接池
  18. 微信全面开放个人免费版红包封面!
  19. Java语言西安交大高起专_2018年西安交大网络学院高起专/本英语入学考试复习题...
  20. SQL Server视频

热门文章

  1. Flutter漫说:组件生命周期、State状态管理及局部重绘的实现(Inherit)
  2. 2019.4.17 映纷创意首页铺设练习
  3. jira 插件介绍地址
  4. 20165115 第二周学习总结
  5. SaltStack部署
  6. 连接关键词用什么符号
  7. 一队老耗子,每个月都生一对小耗子,小耗子长3个月,第四个月开始变成老耗子开始生, // 假设所有耗子都不死,请问20个月之后共有多少只耗子?...
  8. linux中用anaconda使用不同版本python
  9. CentOS7安装MySQL8.0图文教程
  10. centos7搭建时间服务器