HoloLens2 × ZIGSIM やーる（ARKitの位置情報を使って水面をアゲサゲ）

# はじめに
[HoloLensアドベントカレンダー2021](https://qiita.com/advent-calendar/2021/hololens)の10日目の記事です。
今回はZIGSIMを用いて、iPhoneのARKit（位置情報）を使って水面をアゲサゲしてみましょう。

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

# 実装

1．[1日目の記事](https://qiita.com/SatoshiGachiFujimoto/items/d9616b0cb08e17a17baf)を参考にセットアップお願いします

2．[【シェーダーグラフメモ その50】Voronoiを利用した水面エフェクト](https://r-ngtm.hatenablog.com/entry/2019/07/31/084849)を参考に水面シェーダーを作ります。ShaderGraphからShaderLabに移植したものが下記になります

~~~WaterSurface.shader
Shader "Unlit/WaterSurface"
{
    Properties
    {
        _TimeScale("TimeScale", Float) = 0.3
        _CellSpeed("CellSpeed", Float) = 3.5
        _CellDensity("CellDensity", Float) = 14
        _Power("Power", Float) = 2.38
        _WhiteSmoothstep("WhiteSmoothstep", Vector) = (-0.47, 1.97, 0.0, 0.0)
        _OverColor("OverColor", Color) = (1, 1, 1, 1)
        _BaseColor("BaseColor", Color) = (0.3622641, 0.627297, 1.0, 0.0)
        _UvDistortionSpeed("UvDistortionSpeed", Float) = 0.1
        _DistortionNoiseScale("DistortionNoiseScale", Float) = 63
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" }
        LOD 100

        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            // make fog work
            #pragma multi_compile_fog

            #include "UnityCG.cginc"

            float _TimeScale;
            float _CellSpeed;
            float _CellDensity;
            float _Power;
            float4 _WhiteSmoothstep;
            float4 _OverColor;
            float4 _BaseColor;
            float _UvDistortionSpeed;
            float _DistortionNoiseScale;

            void Unity_Multiply_float(float A, float B, out float Out)
            {
                Out = A * B;
            }

            void Unity_Multiply_float4(float4 A, float4 B, out float4 Out)
            {
                Out = A * B;
            }


            void Unity_Add_float2(float2 A, float2 B, out float2 Out)
            {
                Out = A + B;
            }

            void Unity_Add_float4(float4 A, float4 B, out float4 Out)
            {
                Out = A + B;
            }


            inline float Unity_SimpleNoise_RandomValue_float(float2 uv)
            {
                return frac(sin(dot(uv, float2(12.9898, 78.233)))*43758.5453);
            }

            inline float Unity_SimpleNnoise_Interpolate_float(float a, float b, float t)
            {
                return (1.0-t)*a + (t*b);
            }

            inline float Unity_SimpleNoise_ValueNoise_float(float2 uv)
            {
                float2 i = floor(uv);
                float2 f = frac(uv);
                f = f * f * (3.0 - 2.0 * f);

                uv = abs(frac(uv) - 0.5);
                float2 c0 = i + float2(0.0, 0.0);
                float2 c1 = i + float2(1.0, 0.0);
                float2 c2 = i + float2(0.0, 1.0);
                float2 c3 = i + float2(1.0, 1.0);
                float r0 = Unity_SimpleNoise_RandomValue_float(c0);
                float r1 = Unity_SimpleNoise_RandomValue_float(c1);
                float r2 = Unity_SimpleNoise_RandomValue_float(c2);
                float r3 = Unity_SimpleNoise_RandomValue_float(c3);

                float bottomOfGrid = Unity_SimpleNnoise_Interpolate_float(r0, r1, f.x);
                float topOfGrid = Unity_SimpleNnoise_Interpolate_float(r2, r3, f.x);
                float t = Unity_SimpleNnoise_Interpolate_float(bottomOfGrid, topOfGrid, f.y);
                return t;
            }

            void Unity_SimpleNoise_float(float2 UV, float Scale, out float Out)
            {
                float t = 0.0;

                float freq = pow(2.0, float(0));
                float amp = pow(0.5, float(3-0));
                t += Unity_SimpleNoise_ValueNoise_float(float2(UV.x*Scale/freq, UV.y*Scale/freq))*amp;

                freq = pow(2.0, float(1));
                amp = pow(0.5, float(3-1));
                t += Unity_SimpleNoise_ValueNoise_float(float2(UV.x*Scale/freq, UV.y*Scale/freq))*amp;

                freq = pow(2.0, float(2));
                amp = pow(0.5, float(3-2));
                t += Unity_SimpleNoise_ValueNoise_float(float2(UV.x*Scale/freq, UV.y*Scale/freq))*amp;

                Out = t;
            }

            void Unity_Remap_float(float In, float2 InMinMax, float2 OutMinMax, out float Out)
            {
                Out = OutMinMax.x + (In - InMinMax.x) * (OutMinMax.y - OutMinMax.x) / (InMinMax.y - InMinMax.x);
            }

            inline float2 Unity_Voronoi_RandomVector_float (float2 UV, float offset)
            {
                float2x2 m = float2x2(15.27, 47.63, 99.41, 89.98);
                UV = frac(sin(mul(UV, m)));
                return float2(sin(UV.y*+offset)*0.5+0.5, cos(UV.x*offset)*0.5+0.5);
            }

            void Unity_Voronoi_float(float2 UV, float AngleOffset, float CellDensity, out float Out, out float Cells)
            {
                float2 g = floor(UV * CellDensity);
                float2 f = frac(UV * CellDensity);
                float t = 8.0;
                float3 res = float3(8.0, 0.0, 0.0);

                for(int y=-1; y<=1; y++)
                {
                    for(int x=-1; x<=1; x++)
                    {
                        float2 lattice = float2(x,y);
                        float2 offset = Unity_Voronoi_RandomVector_float(lattice + g, AngleOffset);
                        float d = distance(lattice + offset, f);

                        if(d < res.x)
                        {
                            res = float3(d, offset.x, offset.y);
                            Out = res.x;
                            Cells = res.y;
                        }
                    }
                }
            }

            void Unity_Power_float(float A, float B, out float Out)
            {
                Out = pow(A, B);
            }

            void Unity_Smoothstep_float(float Edge1, float Edge2, float In, out float Out)
            {
                Out = smoothstep(Edge1, Edge2, In);
            }

            struct appdata
            {
                float4 vertex : POSITION;
                float2 uv : TEXCOORD0;
            };

            struct v2f
            {
                float2 uv : TEXCOORD0;
                float4 vertex : SV_POSITION;
            };

            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv = v.uv;
                return o;
            }

            float4 frag (v2f i) : SV_Target
            {
                // STEP 1 : Voronoiを使って水面のベースを作る
                float _Multiply_Out;
                Unity_Multiply_float(_Time.x, _TimeScale, _Multiply_Out);

                float _Multiply_Out_2;
                Unity_Multiply_float(_Multiply_Out, _CellSpeed, _Multiply_Out_2);
            
                // STEP3 : UVにノイズを混ぜる(完成)
                float _Multiply_Out_5;
                Unity_Multiply_float(_Multiply_Out, _UvDistortionSpeed, _Multiply_Out_5);

                float2 _Add_Out_2;
                Unity_Add_float2(i.uv, _Multiply_Out_5.xx, _Add_Out_2);

                float _SimpleNoise_Out;
                Unity_SimpleNoise_float(_Add_Out_2, _DistortionNoiseScale, _SimpleNoise_Out);

                float _Remap_Out;
                Unity_Remap_float(_SimpleNoise_Out, float2(0.0, 1.0), float2(-0.5, 0.5), _Remap_Out);

                float _Multiply_Out_6;
                Unity_Multiply_float(_Remap_Out, 0.1, _Multiply_Out_6);

                float2 _Add_Out_3;
                Unity_Add_float2(i.uv, _Multiply_Out_6.xx, _Add_Out_3);
                // return float4(_Add_Out_3, 0,0);

                // STEP1のVoronoiの入力に
                float _Voronoi_Out;
                float _Voronoi_Cells;
                Unity_Voronoi_float(_Add_Out_3, _Multiply_Out_2, _CellDensity, _Voronoi_Out, _Voronoi_Cells);

                float _Power_Out_2;
                Unity_Power_float(_Voronoi_Out, _Power, _Power_Out_2);

                float _Smoothstep_Out;
                Unity_Smoothstep_float(_WhiteSmoothstep[0], _WhiteSmoothstep[1], _Power_Out_2, _Smoothstep_Out);
                // return _Smoothstep_Out.xxxx;

                // STEP2. 色を付ける
                float _Multiply_Out_3;
                Unity_Multiply_float(_OverColor[3], _Smoothstep_Out, _Multiply_Out_3);

                float4 _Multiply_Out_4;
                Unity_Multiply_float4(_OverColor, _Multiply_Out_3.xxxx, _Multiply_Out_4);

                float4 _Add_Out;
                Unity_Add_float4(_BaseColor, _Multiply_Out_4, _Add_Out);
                return _Add_Out;
            }
            ENDCG
        }
    }
}
~~~

3．マテリアルを作成し、Planeにアタッチしましょう
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/63863/48411fca-cf5b-12bd-2293-dc2ba369dbc5.png)

4．ZigSimWaterSurfaceManager.csを作成し、Planeをアタッチします

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/63863/46d2c556-2cb8-ce97-434c-3b2544131277.png)

~~~ZigSimWaterSurfaceManager.cs
using System;
using System.Linq;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using TMPro;

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

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

    void OnMessage(Osc.Message msg)
    {
        // Debug.LogFormat("{0} => {1}", msg.path, msg.data[0]);
        if (String.Equals(msg.path,"/ZIGSIM/{Device_UUID}/arkitposition")){
            float posy = (float)msg.data[1];
            plane.transform.position = new Vector3(0.0f, posy-1.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();

    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()) {
            byte[] buffer = new byte[1024];
            await stream.ReadAsync(buffer, 0, 1024);
            lock (lockObject) {
                osc.FeedData(buffer);
            }
        }
    }
#endif
}
~~~

Debug.LogFormat("{0} => {1}", msg.path, msg.data[0]);のところで、ZIGSIMから下記のようなメッセージが送られてきます。

/{Device_UUID}/deviceinfo => iPhone13,3
/ZIGSIM/{Device_UUID}/arkitposition => -0.01249655
/ZIGSIM/{Device_UUID}/arkitrotation => -0.0370841

msg.pathがarkitpositionのときに、msg.data[0]~[2]がそれぞれx,y,zになります。
Planeのyの値を変化させましょう。

5．デプロイして、ZIGSIMを起動します。下記の設定でStartし、iPhoneを上下させると水面も上下すれば成功です！

|Sensor|Setting|Start|
|:--|:--|:--|
|![264133344_593879755004127_8579732609732418049_n.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/63863/a50f70d6-4be6-d87a-69e9-c99ce8140acf.jpeg)|![264037270_1085044185666335_8374676234995130039_n.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/63863/6ac3a2e4-c1b5-81dd-5067-56adafc074e7.jpeg)|![265388215_2733134830316514_2378475078834609276_n.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/63863/f5f2697f-d5a6-687e-2c8a-d535fe29cca9.jpeg)|


#デモ
<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">水面をアゲサゲ<a href="https://twitter.com/hashtag/HoloLens?src=hash&amp;ref_src=twsrc%5Etfw">#HoloLens</a> <a href="https://twitter.com/hashtag/MRTK?src=hash&amp;ref_src=twsrc%5Etfw">#MRTK</a> <a href="https://twitter.com/hashtag/Unity?src=hash&amp;ref_src=twsrc%5Etfw">#Unity</a> <a href="https://twitter.com/hashtag/ZIGSIM?src=hash&amp;ref_src=twsrc%5Etfw">#ZIGSIM</a> <a href="https://t.co/5H0CggatXt">pic.twitter.com/5H0CggatXt</a></p>&mdash; がちもとさん@メタバース熊本 (@sotongshi) <a href="https://twitter.com/sotongshi/status/1469495170093940736?ref_src=twsrc%5Etfw">December 11, 2021</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

お疲れ様でした