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

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/weixin_33898233/article/details/94348995

智能推荐

docker- 构建 oracle2c-r2(12.2.0.1) 的镜像-程序员宅基地

文章浏览阅读423次。需求由于公司的数据库需要使用新的oracle版本(12c-r2 -->12.2.0.1),从之前的oracle11g迁移到12c。所以,便有了我们今天的内容。首先,我们就先来介绍一下如何构建oracle12c的镜像(docker image)。如果大家有使用的需求而又不是正式的项目,可以直接到docker hub 上面 pull 一个别人家的。在这里附上链接:https://hu..._/opt/database/install/unzip -qqqo ../stage/components/oracle.jdk/1.8.0.91.0/

寻(光阴的故事)-程序员宅基地

文章浏览阅读101次。寻寻一束芬芳的鲜花插于窗前在我疲劳的时候 看它一眼寻一把眷恋的故土带在身边在我思乡的时候 捧于胸前寻一段纯真的恋情藏在心间在我寂寞的时候 把她思念 ...

ZStack实践汇 | ZStack平台的使用心得-程序员宅基地

文章浏览阅读2.1k次。作者:ZStack 社区 王彬Iaas云服务的普及,让我们在使用服务器的时候享受了飞一般的感觉,新兴企业在构建自己的系统时,往往都会选择购买云厂商的云服务器(虚拟机)进行使用,使用这样的虚拟机企业不需要购置任何硬件,不需要考虑机房、网络、服务器维护相关的工作便可以获取到一个低成本、安全免维护、伸缩性强、可灵活迁移的云服务器。在这个云服务器上我们可以快速的构建企业的业务系统。随着企业的不断发展,...

Spec2006使用说明-程序员宅基地

文章浏览阅读1.7k次。Spec2006使用说明五 10 十月 2014Bypenglee工具介绍SPEC CPU 2006 benchmark是SPEC新一代的行业标准化的CPU测试基准套件。重点测试系统的处理器,内存子系统和编译器。这个基准测试套件包括的SPECint基准和SPECfp基准。 其中SPECint2006基准包含12个不同的基准测试和SPECfp2006年基准包含19个不..._spec2006如何手动停止

电感、电容贮能公式的推导_平板电容储能计算公式推导-程序员宅基地

文章浏览阅读1.8w次,点赞13次,收藏61次。电感、电容贮能公式及推导过程电感贮能公式。电容贮能公式。由电流定义得到,电容电流为:则电容的贮能公式为:_平板电容储能计算公式推导

【STM32单片机】智能时钟设计_用stm32完成智能时钟-程序员宅基地

文章浏览阅读1.1k次,点赞36次,收藏14次。本项目使用STM32F103C8T6单片机控制器,使用无源蜂鸣器模块、IIC OLED显示模块、DS18B20温度传感器、独立按键等。主要功能:系统运行后,OLED显示温度、日期和时间;可按下K3键进入时间修改模式,K4键用于选择修改位置,K1和K2键调节;再次按下K3键进入闹钟设置模式,K4键用于选择修改位置,K1和K2调节;当打开闹钟开关后,到达设定闹钟时间,开始闹铃,30S后自动关闭,也可通过K3键手动关闭闹铃。_用stm32完成智能时钟

随便推点

s3c6410 jpeg编码 linux,立宇泰ARMSYS6410开发板推出三个linux系统版本-程序员宅基地

文章浏览阅读91次。ARMSYS6410采用了Linux-2.6.28作为标准版的linux内核,其中集成了丰富的驱动资源,充分展现S3C6410的各项特性,包括硬件编解码、2D/3D加速、显示协处理、TVOUT输出、视频采集和编码、4路串口、2路SD/MMC接口、1路10/100M以太网接口、1路USB host接口等等,使ARMSYS6410成为目前linux配置最为强劲和最完整的开发板之一。ARMSYS6410..._s3c6410可以刷那个版本linux

java参数-xmn1g_假如某个JAVA进程的JVM参数配置如下:-Xms1G&nb-程序员宅基地

文章浏览阅读618次。Xms 起始内存Xmx 最大内存Xmn 新生代内存Xss 栈大小。 就是创建线程后,分配给每一个线程的内存大小-XX:NewRatio=n:设置年轻代和年老代的比值。如:为3,表示年轻代与年老代比值为1:3,年轻代占整个年轻代年老代和的1/4-XX:SurvivorRatio=n:年轻代中Eden区与两个Survivor区的比值。注意Survivor区有两个。如:3,表示Eden:Survivor..._-xmn1g

LKD:中断_neil中断-程序员宅基地

文章浏览阅读238次。中断请求(IRQ)线:不同设备对应的中断不同,而每个中断都通过一个唯一的数字标志。重点在于特定的中断总是与特定的设备相关联,并且内核要知道这些信息。 异常:常常也称为同步中断。如处理器执行到由于编程失误导致的错误指令(如被0除),或者执行期间出现特殊情况(如缺页),处理器就会产生一个异常。 中断处理程序(ISR):上半部——接收到一个中断,它就立即开始执行,但只做有严格时限的工作,例如对接收的..._neil中断

通过sqldeveloper导出sql_sqldeveloper导出连接信息-程序员宅基地

文章浏览阅读3.2k次。选择 工具→ 数据库卸载..1、选择要导出的数据库连接,以及导出的文件路径名称2、选择要导出的对象类型,只导出表的话,只勾选 表 即可3、点击查找可以显示数据库的全部对象,选择要导出的对象,>>可以全部选中右移,则进行全部导出。>用来指定导出4、对象的数据,可在where栏填写导出条件,如rownum5、点击完成开始导出。_sqldeveloper导出连接信息

vue+node全栈移动商城【5】-简单的筛选搜索功能-程序员宅基地

文章浏览阅读47次。现在咱们来实现一个简单的搜索功能。不需要数据库,更不需要存取数据,只是单纯的让搜索这个功能运行起来。先来说一下,在前端的层面上,对于搜索大家不要想的太过于复杂。搜索当然可以做的非常复杂,例如百度。但是搜索也可以非常简单。简单的说,无非就是你发送一个关键词到后端,后端对已有的数据进行一个筛选,如果有与关键词相同的,就认为找到结果,并将...

Ant Design 表格table 内容超出 溢出、隐藏 、截断_antd 表格 内容溢出-程序员宅基地

文章浏览阅读2.1w次,点赞3次,收藏12次。首先表格要定义上宽度{ title: '内容', align: 'center', dataIndex: 'contents', width:'45%', //这里使用插槽 scopedSlots: { customRender: 'titleShow' },},不可以使用span标签我使用的是div标签title是用来鼠标悬停进行全部内容显示<..._antd 表格 内容溢出

推荐文章

热门文章

相关标签