osg操控器之动画路径操控器osgGA::AnimationPathManipulator分析

news2025/1/11 17:03:56

目录

1. 前言

2. 示例代码

3. 动画路径操控器源码分析

3.1. 构造函数

3.2. home函数

3.3. handle函数

3.3.1 帧事件处理

3.3.2. 按键事件处理

4. 主要接口说明


1. 前言

      osg官方提供了很多操控器,在源码目录下的src\osgGA目录下,cpp文件名含有Manipulator的都是操控器,每个这样的cpp表示一种类型的操控器。其中AnimationPathManipulator.cpp文件实现了动画路径操控器。

        所谓操控器是指:操控相机运动,从而实现场景视图矩阵变化,反映到观察场景用户的感官上就是:人眼感觉场景变化了,但现实中的物体是没变化的,只是相机方位或距离远近在改变,从而给人视觉上的感觉而已。

        其它操控器参见如下博文:

osg操控器之键盘切换操控器osgGA::KeySwitchMatrixManipulator。

2. 示例代码

           如下用动画路径操控器写的例子,实现奶牛根据预先设定好的路径进行动画动作:

#include<osgViewer/Viewer>
#include<osgGA/AnimationPathManipulator>
#include<osgDB/readFile>
int main(int argc, char* argv[])
{
    osg::ArgumentParser arguments(&argc, argv);

    osgViewer::Viewer viewer(arguments);
    auto pNode = osgDB::readNodeFile(R"(E:\osg\OpenSceneGraph-Data\cow.osg)");
    if (nullptr == pNode)
    {
        OSG_WARN << "file not exist!\r\n";
        return 1;
    }

    auto pAnimationMani = new osgGA::AnimationPathManipulator("AnimationPath.txt");
    viewer.setCameraManipulator(pAnimationMani);
    viewer.setSceneData(pNode);
    return viewer.run();
}

结果如下:

                                                               图1

       按键盘p键,停止动画;再按p键又开启动画。按键盘(键,则减慢动画速率到原速率的1.1倍、按键盘)键,则加快动画速率到原速率的1.1倍。

3. 动画路径操控器源码分析

3.1. 构造函数

       osg动画路径操控器是osgGA::AnimationPathManipulator类实现的。该类有两个构造函数,分别如下:

AnimationPathManipulator::AnimationPathManipulator(osg::AnimationPath* animationPath)
{
      ..... // 为节省篇幅,代码略。
}

AnimationPathManipulator::AnimationPathManipulator( const std::string& filename )
{
    ..... // 为节省篇幅,其它代码略。

    _animationPath->read(in);

}

       第1个构造函数参数是指向动画路径对象的指针。动画路径用osg::AnimationPath类表达,该类封装时间变化模型变换的对应关系,被用来更新相机位置和模型位置。

       第2个构造函数参数是指向包含动画路径对象参数的文件名。动画路径对象参数文件的格式是一般的ASCII文本文件,每行8个float数据,每个数据以空格隔开即可,这8个数值按如下格式组织:                                          

time  px py pz ax ay az aw

       其中time表示动画帧所在的时刻,以秒为单位,该时刻是距离动画开始时逝去的秒数;px、 py、 pz分别表示模型在笛卡尔坐标系下的世界坐标; ax、ay、az、aw以四元组形式表示模型方向、姿态。本文第2节中的AnimationPath.txt是一个自己制作的动画路径对象参数文件,内容如下:

0.2	1.0	1.0	1.0	0.5	1.0	1.5	2.0
0.5	2.0	2.0	2.0	1.0	1.5	2.0	2.5
0.8	3.0	3.0	3.0	1.5	2.0	2.5	3.0
1.2	4.0	4.0	4.0	2.0	2.5	3.0	3.5
1.6	5.0	5.0	5.0	2.5	3.0	3.5	4.0
2.0	6.0	6.0	6.0	3.0	3.5	4.0	4.5

当调用第2个构造函数时,会通过调用如下代码:

 _animationPath->read(in);

将上述文件每行中的8个数值会成为动画路径对象的ControlPoint点(动画帧控制点),read实现如下:

void AnimationPath::read(std::istream& in)
{
    while (!in.eof())
    {
        double time;
        osg::Vec3d position;
        osg::Quat rotation;
        in >> time >> position.x() >> position.y() >> position.z() >> rotation.x() >> rotation.y() >> rotation.z() >> rotation.w();
        if(!in.eof())
            insert(time,osg::AnimationPath::ControlPoint(position,rotation));
    }
}

void AnimationPath::insert(double time,const ControlPoint& controlPoint)
{
    _timeControlPointMap[time] = controlPoint;
}

可以看到,将每行8个点读入后存入到map中(_timeControlPointMap是std::map类型),map的key是time,value是ControlPoint点(动画帧控制点)。

3.2. home函数

   该类中home函数有两个,这里只讲如下home函数:

void AnimationPathManipulator::home(double currentTime)
{
    if (_animationPath.valid())
    {
        _timeOffset = _animationPath->getFirstTime()-currentTime;

    }
    // reset the timing of the animation.
    _numOfFramesSinceStartOfTimedPeriod=-1;
}

     当启动2节的例子时,osg外层框架会调用该函数。因为事件才刚开始,所以参数currentTime一般为0。home函数的作用是:记录动画第一帧离起始时间currentTime的偏移量,将该时间偏移量保存在成员变量_timeOffset中;_numOfFramesSinceStartOfTimedPeriod表示动画运行到当前时刻的帧索引(0表示第1帧)。因为执行home时,动画还未开启,故这里将_numOfFramesSinceStartOfTimedPeriod置为-1,为后续动画的开始计数作准备。

3.3. handle函数

3.3.1 帧事件处理

     所谓帧事件是每帧都会处理的事件,其用GUIEventAdapter::FRAME表示,其在handleFrame函数实现,代码如下:

void AnimationPathManipulator::handleFrame( double time )
{
    osg::AnimationPath::ControlPoint cp;

    double animTime = (time+_timeOffset)*_timeScale;
    _animationPath->getInterpolatedControlPoint( animTime, cp );

    if (_numOfFramesSinceStartOfTimedPeriod==-1)
    {
        _realStartOfTimedPeriod = time;
        _animStartOfTimedPeriod = animTime;

    }

    ++_numOfFramesSinceStartOfTimedPeriod;

    double animDelta = (animTime-_animStartOfTimedPeriod);
    if (animDelta>=_animationPath->getPeriod())
    {
        if (_animationCompletedCallback.valid())
        {
            _animationCompletedCallback->completed(this);
        }

        if (_printOutTimingInfo)
        {
            double delta = time-_realStartOfTimedPeriod;
            double frameRate = (double)_numOfFramesSinceStartOfTimedPeriod/delta;
            OSG_NOTICE <<"AnimatonPath completed in "<<delta<<" seconds, completing "<<_numOfFramesSinceStartOfTimedPeriod<<" frames,"<<std::endl;
            OSG_NOTICE <<"             average frame rate = "<<frameRate<<std::endl;
        }

        // reset counters for next loop.
        _realStartOfTimedPeriod = time;
        _animStartOfTimedPeriod = animTime;
        _numOfFramesSinceStartOfTimedPeriod = 0;
    }

    cp.getMatrix( _matrix );
}

要理解上述代码,需借助如下的时间流逝图:

 图2

 其中紫色时间线的time表示handle调用以来即当前帧经过的总时间,其是以时刻0为参考点的。而实际中帧的起始时间参考点是_timeOffset,故在_timeScale为1时(即没有调节速率),当前帧时刻为:

_timeOffset + time

而帧速率可以通过按下键盘(、)键或通过接口setTimeScale还变快或变慢,故当前帧时刻还需乘以_timeScale,即为:

(time+_timeOffset)*_timeScale

if语句判断如果发现是第1帧,则记录当前实际时间(基于0时刻)和动画帧基于_timeOffset为基准的时刻,便于后面计算动画总时长。之后将帧索引加1。一旦发现当前帧和基准时间大于等于动画总周期(总时长),则表示动画完成,如果安装的动画完成回调对象有效,则调用动画完成回调对象的completed函数,则该函数可以打印出一些信息等。

3.3.2. 按键事件处理

       里面的代码很简单,结合图2的时间线理解不难。需要注意是:当按p暂停后再按p继续动画时,_timeOffset的值要从当前时刻减去暂停时刻,因为这段时间内,动画根本没动。

4. 主要接口说明

         如果需要调用osgGA::AnimationPathManipulator类,经常用到的接口说明如下:

  void setTimeScale(double s);
  double getTimeScale() const;

 设置、获取时间缩放因子,即上面提到的_timeScale,该值越大,动画越快;否则越慢。

void setAnimationCompletedCallback(AnimationCompletedCallback* acc);
AnimationCompletedCallback* getAnimationCompletedCallback();
const AnimationCompletedCallback* getAnimationCompletedCallback() const ;

设置、获取动画完成后的回调对象,用于动画完成时,通知外层调用方,外层调用方收到这个通知后,可以进行处理一些事情。注意:自定义的动画回调类必须从下面AnimationCompletedCallback 继承并实现completed方法,该方法的参数为AnimationPathManipulator类对象,一般情况下为动画路径对象本身。

struct AnimationCompletedCallback : public virtual osg::Referenced
{
    virtual void completed(const AnimationPathManipulator* apm) = 0;
};

可以通过如下函数: 

 void setPrintOutTimingInfo(bool printOutTimingInfo);
 bool getPrintOutTimingInfo() const;

获取或设置是否需要输出动画的一些时间线信息,如:动画帧总数、动画平均帧率、动画总时长等。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/475613.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

初识AUTOSAR

目录 应用层 Runnable Port 运行时环境 基础软件层 总结 AUTOSAR&#xff0c;全称为Automotive Open System Architecture&#xff0c;即汽车开放系统架构。它最初于2003年由当时全球各家顶级汽车制造商&#xff08;奔驰、宝马、大众等&#xff09;、零部件供应商&#x…

【Unity入门】21.预制体

【Unity入门】预制体 大家好&#xff0c;我是Lampard~~ 欢迎来到Unity入门系列博客&#xff0c;所学知识来自B站阿发老师~感谢 &#xff08;一&#xff09;预制体制作 &#xff08;1&#xff09;什么是预制体 这一章节的博客&#xff0c;我们将会学习一个预制体的概念。什么是…

【C语言进阶】-- 重点字符串函数内存函数及其模拟实现(strlen,strcmp,strcat...memcpy,memmove)

目录 1、strlen 1.1 strlen的模拟实现 2、strcpy 2.1 strcpy的模拟实现 3、strcat 3.1 strcat的模拟实现 4、strcmp 4.1 strcmp的模拟实现 5、strstr 5.1 strstr的模拟实现 6、memcpy 6.1 memcpy的模拟实现 7、memmove 7.1 memmove的模拟实现 前言 C语言中对字符…

Ant Design Vue,a-table组件加序号

<a-table:columns"columns":pagination"pagination":data-source"dataSource":defaultExpandAllRows"true"change"tableChange":rowKey"(record, index) > index 1"> columns是表格列的配置&#xff0c…

【2023五一杯数学建模】 B题 快递需求分析问题 建模方案及MATLAB实现代码

【2023五一杯数学建模】 B题 快递需求分析问题 1 题目 请依据以下提供的附件数据和背景内容&#xff0c;建立数学模型&#xff0c;完成接下来的问题&#xff1a;问题背景是&#xff0c;网络购物作为一种重要的消费方式&#xff0c;带动着快递服务需求飞速增长&#xff0c;为我…

25特别放送:我的Gopher成长之路

很早就开始准备这篇文章了,但总是想了又想不知怎样才能更好的写下自己最真实的想法,后来在经过了好几个晚上睡前的思考后才得以完成。 首先,写这篇文章的目的并不是为了吹嘘Go语言有多厉害,也不是鼓励大家都来学习Go语言,仅是为了记录和分享。当然如果是兴趣使然,那么欢…

实时更新天气微信小程序开发

1.新建一个天气weather项目 2.在app.json中创建一个路由页面 当我们点击保存的时候&#xff0c;微信小程序会自动的帮我们创建好页面 3.在weather页面上书写我们的骨架 4.此时我们的页面很怪&#xff0c;因为没有给它添加样式和值。此时我们给它一个样式。&#xff08;样式写在…

蓝桥杯——二分专题

二分分为&#xff1a;实数二分&#xff0c;二分理论题 二分套路题&#xff1a;最小值最大化&#xff0c;最大值最小化 运用二分满足条件&#xff1a;有界&#xff0c;单调。 1.两个二分模板 找>x的第一个&#xff0c;mid&#xff08;lowhigh&#xff09;//2 &#xff0c;没…

java基础知识——23.正则表达式

这篇文章我们简略的讲一下java的正则表达式 目录 1.正则表达式概述 2.正则表达式的简单匹配规则 3.正则表达式的复杂匹配规则 4.正则表达式的分组匹配规则 5.正则表达式的非贪婪匹配 6.使用正则表达式进行搜索和替换 1.正则表达式概述 首先&#xff0c;我们需要明确一个…

leetcode 面试题 02.04. 分割链表

原题为&#xff1a; 给你一个链表的头节点 head 和一个特定值 x &#xff0c;请你对链表进行分隔&#xff0c;使得所有 小于 x 的节点都出现在大于或等于x 的节点之前。 你不需要 保留 每个分区中各节点的初始相对位置。 测试示例如下&#xff1a; 输入&#xff1a;head [1,4…

Flink第一章:环境搭建

系列文章目录 Flink第一章:环境搭建 文章目录 系列文章目录前言一、Idea项目1.创建项目2.pom.依赖3.DataSet4.DataStreaming 二、环境搭建1.Standalone2.Flink on Yarn 总结 前言 Flink也是现在现在大数据技术中火爆的一门,反正大数据的热门技术学的也差不多了,啃完Flink基本…

Packet Tracer - 研究直连路由

Packet Tracer - 研究直连路由 目标 第 1 部分&#xff1a;研究 IPv4 直连路由 第 2 部分&#xff1a;研究 IPv6 直连路由 拓扑图 背景信息 本活动中的网络已配置。 您将登录路由器并使用 show 命令发现并回答以下有关直连路由的问题。 注&#xff1a;用户 EXEC 密码是 c…

A2B汽车音响系统开发设计与改装

hezkz17进数字音频系统研究开发答疑群 1 前装与后装

安装了Volar插件vue文件没有显示Volar的图标

vue3官网 推荐使用Volar来替换Vetur 一、安装Volar 安装Volar前&#xff1a; 安装Volar后&#xff1a; 二、安装Volar插件后&#xff0c;无法显示高亮 之前我安装Volar插件后&#xff0c;vue文件的<script>、<template>、<style>标签仍然是白色的&#xff0c…

Doris(17):动态分区

动态分区是在 Doris 0.12 版本中引入的新功能。旨在对表级别的分区实现生命周期管理(TTL)&#xff0c;减少用户的使用负担。 目前实现了动态添加分区及动态删除分区的功能。 1 原理 在某些使用场景下&#xff0c;用户会将表按照天进行分区划分&#xff0c;每天定时执行例行任…

【网课平台】Day14.集成RabbitMQ:消息队列实现异步通知

文章目录 一、需求&#xff1a;支付通知1、需求分析2、技术方案3、集成RabbitMQ4、生产端发送消息5、消费方发送消息 二、需求&#xff1a;在线学习1、需求分析2、表设计与实体类3、接口定义--查询课程4、接口定义获取视频5、Service层开发6、FeignClient定义7、代码完善 三、需…

数字设计小思 - D触发器与死缠烂打的亚稳态

前言 本系列整理数字系统设计的相关知识体系架构&#xff0c;为了方便后续自己查阅与求职准备。在FPGA和ASIC设计中&#xff0c;D触发器是最常用的器件&#xff0c;也可以说是时序逻辑的核心&#xff0c;本文根据个人的思考历程结合相关书籍内容和网上文章&#xff0c;聊一聊D…

Hudi数据湖技术之数据中心案例实战

目录 1 案例架构2 业务数据2.1 客户信息表2.2 客户意向表2.3 客户线索表2.4 线索申诉表2.5 客户访问咨询记录表 3 Flink CDC 实时数据采集3.1 开启MySQL binlog3.2 环境准备3.3 实时采集数据3.3.1 客户信息表3.3.2 客户意向表3.3.3 客户线索表3.3.4 客户申诉表3.3.5 客户访问咨…

微信小程序 WebSocket 通信 —— 在线聊天

在Node栏目就讲到了Socket通信的内容&#xff0c;使用Node实现Socke通信&#xff0c;还使用两个流行的WebSocket 库&#xff0c;ws 和 socket.io&#xff0c;在小程序中的WebSocket接口和HTML5的WebSocket基本相同&#xff0c;可以实现浏览器与服务器之间的全双工通信。那么本篇…

SSH 服务器、NFS 服务器、TFTP 服务器详解及测试

文章目录 前言一、SSH 服务器1、SSH 能做什么&#xff1f;2、安装 SSH 服务器3、测试 SSH 服务4、用 SecureCRT 测试 二、NFS 服务器1、NFS 能做什么&#xff1f;2、安装 NFS 软件包3、添加 NFS 共享目录4、启动 NFS 服务5、测试 NFS 服务器 三、TFTP 服务器1、TFTP 能做什么&a…