ROS2のIMUの情報をもとにUnityのオブジェクトを縦揺れさせる

<h1>はじめに</h1>
<p>今回はROS2とUnityの連携の一環として慣性計測ユニット（IMU）からの情報をUnity上のシミュレータの物体の挙動に反映するという簡単なサンプルを紹介しようと思います。<br>この情報をうまく反映できれば、路面上の凹凸による縦揺れの挙動や、坂道での挙動を表現することができます。 </p>
<p>なお、本記事で行っている内容は、<a href ="https://qiita.com/sfc_nakanishi_lab/items/40ab94a2f19f375f42db">Windows上でUnityとROS2を連携させる (1) ~環境構築編~</a>および、<a href ="https://qiita.com/sfc_nakanishi_lab/items/ad81e16bd52805347534">Windows上でUnityとROS2を連携させる(2) ~簡易型シミュレータ作成編~</a>の延長の内容になります。</p>
<p>また、使用している環境は以下の通りです。概ね、構築編で使用している環境と同じです。</p>
<h3>PC</h3>
<table>
<tr><th>項目</th><th>バージョン/スペック</th></tr>
<tr><td>CPU</td><td>Core i7-9750H</td></tr>
<tr><td>GPU</td><td>NVIDIA GeForce RTX 2070</td></tr>
<tr><td>OS</td><td>Windows10</td></tr>
<tr><td>ROS2</td><td>Foxy Fitzroy</td></tr>
<tr><td>Unity</td><td>2020.3.18f1</td></tr>
</table>

<h3>Raspberry Pi 4</h3>
<table>
<tr><th>項目</th><th>バージョン/スペック</th></tr>
<tr><td>CPU</td><td>1.5GHz クアッドコア Cortex-A72(ARMv8、64bit L1=32KB、L2=1M)</td></tr>
<tr><td>メモリ</td><td>UD-RP4B8：8GB</td></tr>
<tr><td>OS</td><td>Ubuntu Desktop 20.04</td></tr>
<tr><td>ROS2</td><td>Foxy Fitzroy</td></tr>
<tr><td>TurtleBot3</td><td>Waffle Pi</td></tr>
</table>

<h1>Unity側でやること</h1>
<h2>オブジェクトの設定</h2>
<h3>配置</h3>
<p>新しく作成したシーンに、フィールドとなるPlaneと実機のCubeを配置します。CubeオブジェクトはわかりやすいようにTB3と名前を変えておきます。</p>
<h3>RigidBodyの設定</h3>
<p>実機のオブジェクトにRigidBodyをつけます。単にcmd_velで動かすだけならこれでいいのですが、このあとのスクリプトからの操作の関係で、RigidBody内のConstrainsの設定を以下のようにしてください(FreezeRotationのXとZにチェック）。</p>
![constraints.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2035642/98a1dc56-3d27-d1c8-e805-c544ddd5ebaf.png)

<p>これで配置の設定は以上です。

<h2>スクリプトの記述</h2>
<p>Create→C#Srciptで新しくスクリプトを作成し以下のコードを記述してください。

```IMU.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

using Unity.Robotics.ROSTCPConnector;
using RosMessageTypes.Sensor;       
using RosMessageTypes.Geometry;

public class IMU : MonoBehaviour
{
    private Material material;
    public Rigidbody ROS_Object;

    [Range(0f, 100f)]
    public float Mass = 1.0f;

    Vector3 angularVel, linearAcc;

    // Start is called before the first frame update
    void Start()
    {
        ROSConnection.instance.Subscribe<ImuMsg>("/imu", imuVisualizer);
        ROSConnection.instance.Subscribe<TwistMsg>("/cmd_vel", turtlesim_move);
    
    }


    void imuVisualizer(ImuMsg Msg)
    {
        angularVel = new Vector3(
                                    (float)Msg.angular_velocity.x,
                                    (float)Msg.angular_velocity.y,
                                    (float)Msg.angular_velocity.z);

        linearAcc = new Vector3(
                                   (float)Msg.linear_acceleration.x,
                                   (float)Msg.linear_acceleration.y,
                                   (float)Msg.linear_acceleration.z);

        float F = Mass * (linearAcc.z - 9.8f);

        ROS_Object.AddForce(new Vector3(0f, -F, 0f));

        //Debug
        Debug.Log("angular velocity: " + angularVel);
        Debug.Log("linear acceleration: " + linearAcc);
        Debug.Log("force:" + F);
    }

    void turtlesim_move(TwistMsg Msg)
    {
        ROS_Object.velocity = transform.forward * (float)Msg.linear.x * 10;
        ROS_Object.angularVelocity = new Vector3(0, -(float)Msg.angular.z * 1, 0);

    }

}
```
<p>IMUの情報を受け取って反映している部分についてかいつまんで説明します。
<p>関数imuVisualizerの中身を見て下さい。この関数自体、ROSから/imuのトピックをサブスクライブしたときのコールバックになっています。
<p>そもそも/imuから得られる情報は、三軸の加速度と三軸の角速度になっていて、この関数の中では、Vector3で受け取っています。

```cs
//角速度
angularVel = new Vector3((float)Msg.angular_velocity.x,
                         (float)Msg.angular_velocity.y,
                         (float)Msg.angular_velocity.z);

//加速度
linearAcc = new Vector3((float)Msg.linear_acceleration.x,
                        (float)Msg.linear_acceleration.y,
                        (float)Msg.linear_acceleration.z);
```
<p>今回のサンプルで縦揺れを再現したいので、linearAcc.yを使います。ただし、拾ったデータのまま使用すると重力加速度が含まれているので、使う際にはその分を引きます。物体への揺れを反映するのには、RigidbodyのメソッドであるAddForceに力の成分として出力します。

```cs
//Force = mass * acceleration
 float F = Mass * (linearAcc.z - 9.8f);
//オブジェクトに力を伝える
 ROS_Object.AddForce(new Vector3(0f, -F, 0f));
```
<p>パブリック変数として設けているMassは物体の重さの計数になります。各々の実機によって変更を加えてください。
<p>このサンプルでは、Unityから実機へ動きをパブリッシュする機構はないので、TeleopKeyなどで動かしてみてUnityでの動きをぜひ確認してみてください。

<p>以下デモ映像
<iframe width="560" height="315" src="https://www.youtube.com/embed/y3QpaGEKaMg" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>

<h1>リファレンス</h1>
<ul>
<li><a href="https://qiita.com/sfc_nakanishi_lab/items/40ab94a2f19f375f42db">Windows上でUnityとROS2を連携させる (1) ~環境構築編~</a></li>
<li><a href="https://qiita.com/sfc_nakanishi_lab/items/ad81e16bd52805347534">Windows上でUnityとROS2を連携させる(2) ~簡易型シミュレータ作成編~</a></li>
<li><a href="https://docs.unity3d.com/jp/current/ScriptReference/Rigidbody.AddForce.html">Unity Documentation Rigidbody.AddForce</a></li>
</ul>