Open3D C++系列教程(四)动画 Tick事件

前置:

Open3D C++系列教程 (一)环境搭建
Open3D C++系列教程 (二)第一个GUI窗口
Open3D C++系列教程 (三)关于程序异常退出的探讨


该部分介绍如何在让场景中的物体动起来。

1. Tick()事件

要让场景动起来,我们就需要不断地更新场景里物体的状态。
我们可以通过为窗口设置一个TIck事件来设置物体的新状态,这个事件在每个UI Tick(约10毫秒)的时候都会被调用,从而实现动画的效果。
对于gui::Window的实例,可以使用:

SetOnTickEvent(std::function<bool()> callback);

 
 
  • 1

设置一个std::function<bool()>类型的callback函数,并在这个函数里执行需要的动画。
这个callback函数不带任何参数,需要返回一个bool,如果场景和ui发生了变化,则返回true,从而进行重新绘制。
通过下面这样的形式就可以完成一个动画的设置。

auto animation = [&](){
	// do something
	// ...
if ( // ui or scene changed)
	return true;
else
	return false;

}

window->SetOnTickEvent(animation);

2. 设置一个旋转的动画

在之前的内容中,我们创建了10个几何体并直接添加到窗口中,并没有做任何的保存,为了方便进行动画,我们在创建几何体时将其数据和对应的名称保存起来。

std::vector<std::string> names;
std::vector<std::shared_ptr<open3d::geometry::Geometry3D>> geometries;

 
 

此外,我们为每个网格提供一个随机的角速度:

std::vector<double> speeds;
// 生成 (-pi/2, pi/2)的10个随机值,表示每秒旋转的度数。
std::generate_n(std::back_inserter(speeds), 10, [](){ return (rand() % 180 - 90) / 360.0 * 2 * 3.1415926; });

 
 
2.1 绕世界坐标旋转(公转)

定义一个lambda表达式表示一次旋转。

auto revolution = [&]()
    {
        static auto t0 = instance.Now();
        auto t1 = instance.Now();
        auto dt = t1 - t0;
        t0 = t1;
    for (int i = 0; i &lt; 10;++i)
    {
        Eigen::Transform&lt;double, 3, Eigen::Affine&gt; transform{weak_main_scene.lock()-&gt;GetScene()-&gt;GetGeometryTransform(names[i])};
        transform.prerotate(Eigen::AngleAxisd(speeds[i] * dt, Eigen::Vector3d::UnitY()));

        weak_main_scene.lock()-&gt;GetScene()-&gt;SetGeometryTransform(names[i], transform.matrix());
    }

    return true; };

由于这是一个Tick事件,而两次Tick的时间几乎不可能是1s,这一般和渲染速度有关。为了使动画在1s的事件内旋转一个角度

     θ  \theta 
   
  
</span><span class="katex-html"><span class="base"><span class="strut" style="height: 0.6944em;"></span><span class="mord mathnormal" style="margin-right: 0.0278em;">θ</span></span></span></span></span>,我们需要计算两个<code>Tick</code>之间的时间差<code>dt</code>,并让物体在这次<code>Tick</code>中旋转<code>dt*\theta</code>度,这正是代码3-6行所做的工作。</p> 

有了时间dt,接下来对每一个网格进行旋转。
由于每一次的旋转都是在之前旋转的基础上进行的,所以首先从场景中获取当前的几何变换(通过网格的名字来索取),并使用该变换初始化一个Eigen的仿射变换:

Eigen::Transform<double, 3, Eigen::Affine> transform{weak_main_scene.lock()->GetScene()->GetGeometryTransform(names[i])};

 
 

然后在这个变换中左乘一个新的旋转矩阵:

// 绕Y轴旋转 speeds[i] *dt 角度
transform.prerotate(Eigen::AngleAxisd(speeds[i] * dt, Eigen::Vector3d::UnitY()));

 
 

左乘和右乘
设初始化后的transform具有一个绕

      Y  Y 

Y轴旋转

      θ  \theta 

θ的旋转

      A   \mathbf{A} 

A,此外还有一个绕

      Y Y 

Y轴旋转

      beta beta 

beta的旋转矩阵

      B  \mathbf{B} 

B,那么:

  • transform.prerotate(B) 等价于
           B  A  \mathbf{BA} 
    

BA

  • transform.rotate(B) 等价于
  •        A B \mathbf{AB} 
    

    AB

    由于在这个动画中都是围绕同一个轴进行旋转,旋转的顺序并不重要,因此在代码中使用rotate()prerotate()是等价的。
    但在大部分的变换中,一般期望的都是左乘变换矩阵,所以使prerotate()更加合适。

    平移和缩放
    平移和缩放同理,有pretranslate()translate()prescale()scale(),一定要注意变换的顺序。

    获得最终变换后就可以通过网格的名字来为网格设置新的变换:

    weak_main_scene.lock()->GetScene()->SetGeometryTransform(names[i], transform.matrix());
    
     
     

    这就完成了一次旋转变换。

    网格的几何数据
    这里所设置的变换不会对原始的几何数据产生任何影响,只是在渲染中起作用。

    最后让该函数返回true并将该函数设置为Tick事件:

    weak_win.lock()->SetOnTickEvent(revolution);
    
     
     
    2.2 绕局部坐标旋转(自转)

    自转与公转的代码类似,只不过在进行旋转的时候需要先将网格移动到世界原点,然后进行旋转,最后在平移回最初的位置,就可以完成绕自身旋转。

    auto autorotation = [&]()
        {
            static auto t0 = instance.Now();
            auto t1 = instance.Now();
            auto dt = t1 - t0;
            t0 = t1;
    
        for (int i = 0; i &lt; 10;++i)
        {
            auto transform = weak_main_scene.lock()-&gt;GetScene()-&gt;GetGeometryTransform(names[i]);
    
            Eigen::Transform&lt;double, 3, Eigen::Affine&gt; composed = Eigen::Transform&lt;double, 3, Eigen::Affine&gt;::Identity();
            composed = transform;
            composed.pretranslate(-geometries[i]-&gt;GetCenter());
            composed.prerotate(Eigen::AngleAxisd(2*speeds[i] * dt, Eigen::Vector3d::UnitY()));
            composed.pretranslate(geometries[i]-&gt;GetCenter());
    
            weak_main_scene.lock()-&gt;GetScene()-&gt;SetGeometryTransform(names[i], composed.matrix());
        }
    
        return true; };
    

    在上述代码中:

    • 13行:compose表示当前变换;
    • 14行:将物体移动到原点,此时可以看到将几何体保存下来的作用;
    • 15行:绕Y轴旋转一个角度2*sppeds[i]*dt;
    • 16行:将物体平移回之前的位置。

    注意这里使用的是表示左乘的prerotate/pretranslate,如果直接使用rotate/translate将会不会得到预期的结果。

    3. 运行结果

    3.1 公转效果

    公转效果

    3.2 自转效果

    自转效果

    4. 完整代码

    如果不想改代码,或者想要获取直接获取源代码文件,可以通过下载链接进行下载。那么代价是什么呢?

    后续

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值