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

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

前置:

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


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

1. Tick()事件

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

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

设置一个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 < 10;++i)
        {
            Eigen::Transform<double, 3, Eigen::Affine> transform{weak_main_scene.lock()->GetScene()->GetGeometryTransform(names[i])};
            transform.prerotate(Eigen::AngleAxisd(speeds[i] * dt, Eigen::Vector3d::UnitY()));

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

        return true; };

由于这是一个Tick事件,而两次Tick的时间几乎不可能是1s,这一般和渲染速度有关。为了使动画在1s的事件内旋转一个角度 θ \theta θ,我们需要计算两个Tick之间的时间差dt,并让物体在这次Tick中旋转dt*\theta度,这正是代码3-6行所做的工作。

有了时间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轴旋转 b e t a 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 < 10;++i)
        {
            auto transform = weak_main_scene.lock()->GetScene()->GetGeometryTransform(names[i]);

            Eigen::Transform<double, 3, Eigen::Affine> composed = Eigen::Transform<double, 3, Eigen::Affine>::Identity();
            composed = transform;
            composed.pretranslate(-geometries[i]->GetCenter());
            composed.prerotate(Eigen::AngleAxisd(2*speeds[i] * dt, Eigen::Vector3d::UnitY()));
            composed.pretranslate(geometries[i]->GetCenter());

            weak_main_scene.lock()->GetScene()->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. 完整代码

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

后续

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

吉拉尔

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值