HoloLens2 × ZIGSIM やーる（Quaternion）

# はじめに
[HoloLensアドベントカレンダー2021](https://qiita.com/advent-calendar/2021/hololens)の1日目の記事です。
今回は[ZIGSIM](https://apps.apple.com/jp/app/zig-sim-pro/id1481556614)を用いて、iPhoneのQuaternionをHoloLens2にUDP送信し、HoloLens2上のCapsuleをiPhoneの姿勢に合わせて動かしてみましょう。

# 開発環境
- Windows 10 PC
- Unity 2020.3.0f1
- MRTK 2.7.2
- HoloLens2
- iPhone 12 Pro
- ZIG SIM PRO

# 実装

1．MixedRealityFeatureTool-1.0.2104.4-Betaを用いてMRTKをインストールします
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/63863/5c6a51ec-d4c5-5e6d-3f32-041cd83e4e03.png)
MRTK導入部分の詳細はこちらの[HoloLens2 はじめーる（Unity 2020.3、MRTK2.7.2）](https://qiita.com/SatoshiGachiFujimoto/items/92211b6426a5fc3aa937)をご参照ください。

2．ZigSimManager.csを作成し、objにCapsuleをアタッチします

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/63863/8fdc55a4-776d-7f2e-6f4e-71ad77a8c6b6.png)


~~~ZigSimManager.cs
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

#if UNITY_EDITOR
using System.Net;
using System.Net.Sockets;
#else
using System;
using System.IO;
using Windows.Networking.Sockets;
#endif

public class ZigSimManager : MonoBehaviour
{
    int port = 3333;
    Osc.Parser osc = new Osc.Parser();
    public GameObject obj;

    void OnMessage(Osc.Message msg)
    {
        // Debug.LogFormat("{0} => {1}", msg.path, msg.data[0]);
        if (String.Equals(msg.path,"/ZIGSIM/{Device UUID}/quaternion")){
            obj.transform.rotation = new Quaternion(-(float)msg.data[0], -(float)msg.data[2], -(float)msg.data[1], (float)msg.data[3]) * Quaternion.Euler(90.0f, 0.0f, 0.0f);
        }
    }

#if UNITY_EDITOR
    UdpClient udpClient;
    IPEndPoint endPoint;

    void Start()
    {
        endPoint = new IPEndPoint(IPAddress.Any, port);
        udpClient = new UdpClient(endPoint);
    }

    void Update()
    {
        while (udpClient.Available > 0) {
            var data = udpClient.Receive(ref endPoint);
            osc.FeedData(data);
        }

        while (osc.MessageCount > 0) {
            var msg = osc.PopMessage();
            OnMessage(msg);
        }
    }
#else
    DatagramSocket socket;
    object lockObject = new object();

    const int MAX_BUFFER_SIZE = 1024;
    byte[] buffer = new byte[MAX_BUFFER_SIZE];

    async void Start()
    {
        try {
            socket = new DatagramSocket();
            socket.MessageReceived += OnMessage;
            await socket.BindServiceNameAsync(port.ToString());
        } catch (System.Exception e) {
            Debug.LogError(e.ToString());
        }
    }

    void Update()
    {
        lock (lockObject) {
            while (osc.MessageCount > 0) {
                var msg = osc.PopMessage();
                OnMessage(msg);
            }
        }
    }

    async void OnMessage(DatagramSocket sender, DatagramSocketMessageReceivedEventArgs args)
    {
        using (var stream = args.GetDataStream().AsStreamForRead()) {
            await stream.ReadAsync(buffer, 0, MAX_BUFFER_SIZE);
            lock (lockObject) {
                osc.FeedData(buffer);
            }
        }
    }
#endif
}
~~~

Debug.LogFormat("{0} => {1}", msg.path, msg.data[0]);のところで
/{Device UUID}/deviceinfo => iPhone13,3
/ZIGSIM/{Device UUID}/quaternion => 0.3267779
こんな感じのOSCメッセージが送られてきます。{Device UUID}はZIGSIMの設定からコピペしてください。OSCのメッセージの処理については、keijiroさんの[unity-osc](https://github.com/keijiro/unity-osc)を使ってます。

msg.pathがquaternionのときに、msg.data[0]~[3]がそれぞれx,y,z,wになります。
object型になっているのでfloat型にキャストして、Unityの座標系に合わせてCapsuleのrotationに代入します。

3．HoloLens2にデプロイしましょう。PlayerSettings->CapabilitiesのInternetClientServerとPrivateNetworkClientServerにチェック入れるのを忘れずに。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/63863/5f31c0c0-7d87-56ad-793e-4c14d123c272.png)

反映されてないこともあるので、Package.appxmanifestもチェックしましょう。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/63863/6bac459a-4505-8b3b-c945-f246d7135cd0.png)

4．アプリを起動したら、ZIGSIMも起動します。ZIGSIMの設定でHoloLens2のIPアドレスを指定してStartしてください。CapsuleがiPhoneに合わせて動いたら成功です。

|Sensor|Setting|Start|
|:--|:--|:--|
|![262127487_1790216537839892_7255513602478216080_n.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/63863/d226b83b-65fd-be52-ade0-a032bb9b80a0.jpeg)|![262583901_1187764461747415_208444545253038229_n.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/63863/7c4a1b0b-ad13-fd6e-1bc7-ad55b5357232.jpeg)|![263038467_1008804956335039_7598450806399269906_n.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/63863/2f20cb57-5efb-8ff1-49bc-b71c0999aebc.jpeg)|

# デモ

<blockquote class="twitter-tweet"><p lang="fr" dir="ltr"><a href="https://twitter.com/hashtag/HoloLens2?src=hash&amp;ref_src=twsrc%5Etfw">#HoloLens2</a> x <a href="https://twitter.com/hashtag/ZIGSIM?src=hash&amp;ref_src=twsrc%5Etfw">#ZIGSIM</a> quaternion test <a href="https://t.co/FsXFjlfiOr">pic.twitter.com/FsXFjlfiOr</a></p>&mdash; がちもとさん@メタバース熊本 (@sotongshi) <a href="https://twitter.com/sotongshi/status/1465973475999293445?ref_src=twsrc%5Etfw">December 1, 2021</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

# 参考文献
- [HoloLens で UDP 通信を行う方法について調べてみた](https://tips.hecomi.com/entry/2017/04/19/011320)
- [UnityでiOSのジャイロの使い方をちゃんと説明する](https://qiita.com/fuqunaga/items/b1a3e38af71f062f0781)