NeosVR＋Elixirで気軽にVR WebSocketプログラミング（VR |> AR投影アプリの裏側）

この記事は[「NeosVR reso Advent Calendar 2020」](https://qiita.com/advent-calendar/2020/neosvr_reso)、および[「NeosVR Advent Calendar 2020」](https://adventar.org/calendars/5277)の11日目です

昨日は、@nuwaa さんの[「『NeosVR reso』というコミュニティ 」](https://note.com/nuwaa/n/n070a53466232
)でした

---

NeosVRから現実世界に干渉し、生活や仕事をより良いものへと改変していくコミュニティ[「NeosVR reso」](https://neosvr.connpass.com/)のオーガナイザー、[piacere](https://twitter.com/piacere_ex)です
ご覧いただいて、ありがとうございます :bow:

私は、分散・並列データサイエンスに強い「Elixir」というプログラミング言語を2015年から使い始め、2017年以降はコミュニティ「fukuoka.ex」を主催しつつ、業務でも積極登用してて、現在は業務／プライベート共に、ほぼこの言語しか使っていない状態なんですが、国内ではまだまだマイナーな部類の言語です

しかし、世界を見てみると、StackOverflowという有名サイトが行った「世界でもっとも高給取りなプログラミング言語ランキング」で5位に入っている言語で、更にDiscordやSlack、Spotifyをはじめとする世界的サービスでも使われてるんです

<b>StackOverflow</b>
https://insights.stackoverflow.com/survey/2019#top-paying-technologies

<b>上記資料の日本語紹介</b>
https://news.mynavi.jp/article/20190415-807198/

今回、このElixirとNeosVRを繋いで、気軽にVR WebSocketプログラミングをしてみます

内容が、面白かったり、役に立ったら、「LGTM」よろしくお願いします :wink:

### <b>:ocean::ocean::ocean: お知らせ：NeosVR reso Advent Calendar、2位達成ヽ(=´▽`=)ﾉ :ocean::ocean::ocean:</b>

今年初登場のNeosVR reso Advent Calendar、Webテクノロジーカテゴリで2位獲得です:tada:
https://qiita.com/advent-calendar/2020/neosvr_reso
[![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/155423/8a22a81f-2917-6072-10bd-525e0fc24b75.png)](https://qiita.com/advent-calendar/2020/neosvr_reso)

なお、「NeosVR |> AR投影アプリ」で使っているElixirも、プログラミング言語カテゴリで2位でした:laughing:
https://qiita.com/advent-calendar/2020/elixir
[![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/155423/ea185203-c525-c778-dc10-6d2ecc391616.png)](https://qiita.com/advent-calendar/2020/elixir)

# NeosVR |> WebSocket |> Elixirで何ができるの？

その1例として、NeosVR内のオブジェクト情報を、ARアプリに配信し、現実世界と重ね合わせる投影が可能になります

https://twitter.com/piacere_ex/status/1290153498479226880

https://twitter.com/piacere_ex/status/1297540071214559232

https://twitter.com/piacere_ex/status/1330768242000871424

# NeosVRとElixirの悪魔合体でVRと現実世界が繋げられる

## NeosVRの良いところ

NeosVRは、LogiXというプログラミングツールを使って、HTTP GET／POSTやWebSocketをVR内でプログラミングできるため、NeosVRの外にあるAPIや、WebSocket対応しているシステムもしくは機器とカンタンに接続できます

これを使うと、NeosVRから様々な現実のサービスやインフラを利用したり、NeosVR内で起こったことや行ったアクションを現実世界に反映させたり、NeosVRで見ているVR風景を現実に投影させるといったことが可能です

更に、WebSocketであれば、現実で起こった出来事をセンサーやIoTで拾い、NeosVRの中に送り込んで処理することもできるので、現実で誰かが見ている風景をVR内で複数人で同時共有したり、MIDI楽器を打鍵したらNeosVR内で音として鳴らしてみんなで聞く、といったことも可能です

ただしLogiXは、ノードプログラミングであるため、データ処理や演算、認証処理といったインフラ構築やWebサービス開発で頻出するロジック開発にはあまり向いていないので、複雑な処理を行いたい場合は、外部にサーバを立てて、ラップさせた方がラクできます

## Elixirの良いところ

Elixirは、DB接続するAPI開発（REST API）のような、現実のサービスやインフラを作ることが、コマンド1発で出来ますし、WebSocket開発も、「Channel」という機能を使えば、接続確認やサーバ構築といった、面倒なことを一切スッ飛ばし、受け取りたいデータ／流したいデータのことだけを考えて作れるので、一瞬で開発が完了します

更に、JSONやバイナリデータのハンドリング（パース、パターンマッチングによるデータ内容次第での処理分岐）や、それらデータそのものの加工や集計がメチャクチャ強いのも、データサイエンスに強いElixirならではの特徴です

そのため、他サービス／機器のAPIやWebSocketをラップし、Elixirで適切なデータ加工／集計を行うことで、NeosVRと他サービス／機器を繋ぐ際、そのどちらにも最適なデータ交換を提供できます

また、このラップ構成にした場合、DiscordやSlack、Spotifyでやっているような、大量アクセスを処理する負荷分散構成に近くなり、「リクエストの受付」と「本体処理」を分離できるようにもなるため、ユーザ急増やサービスグロースといった「嬉しい悲鳴」が起こっても、性能改善に追われたり、高額なクラウド費用を支払うこと無く、安全にスケールアウトしていくことができます

## そんな2つを悪魔合体させるとどうなる？

端的に言えば、「VR空間」と「現実世界」を、超絶お手軽に繋げられるようになります

それによる恩恵は、今月後半のAdvent Calendarに詳しく書くので、ココではざっくり解説ですが、以下のような世界観が叶います

- 「VR」 → 「現実（＋IoT）」 → 「VR」
 - VRで起こるイベントや起こすアクションで現実にIoTなど経由で干渉し、その結果をセンシングして、VR改変を行う
- 「現実（＋IoT）」 → 「VR＋AI・ML」 → 「現実（＋AR）」
 - 現実で起こるイベントや起こすアクションをVR＋AI・ML内で処理し、その結果に基づいて、サイネージや3Dプリンタなどによって現実改変を行う

これはまさに「Society 5.0」もしくは「インダストリー 4.0」の世界観ですね

<b>Society 5.0 - 科学技術政策 - 内閣府</b>
https://www8.cao.go.jp/cstp/society5_0/
[![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/155423/cc7e4610-2462-ca8b-b294-38604dbb8b48.png)](https://www8.cao.go.jp/cstp/society5_0/)

<b>インダストリー 4.0とは</b>
https://seizo-net.com/industry40/
[![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/155423/b1a0a0b6-01e9-0946-e576-2246b1c37682.png)](https://seizo-net.com/industry40/)

実際、どのような活用方法があるかは、下記コラムをご覧ください

<b>「NeosVR |> AR投影アプリ」が拓く生活と仕事：人はより「現実」へと出ていき、世界が変わったことを理解する</b>
https://qiita.com/piacerex/items/fcb29251e37b1ac3e063

その先に、どのような世界観があるかは、下記コラムをご覧ください

<b>NeosVRに見出した可能性と未来について：「4つの世界」は「7つの世界」に</b>
https://qiita.com/piacerex/items/7c29778e19e5b281f293

# NeosVR＋ElixirでVR WebSocketプログラミング

それでは、これら構想（妄想？）の基本となる、NeosVR＋ElixirによるVR WebSocketプログラミングを行っていきましょう

まず、NeosVR側のWebSocket送信について、LogiXプログラミング解説を行い、その後、ElixirでのWebSocketサーバ構築の解説をします

## NeosVR側の全体像

NeosVR側では、NeosVRオブジェクトの属性を取得して、それをJSON化し、WebSocket送信しています

その全体像は、こんな感じです
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/155423/e0469c11-b7ac-213a-ccbd-16d3783540fd.png)

思ったよりもロジックが少ないように見えますが、それは「BluePrint」というモジュール化を行うツールの影響です

ここから、各モジュールの中身を見ていきます

## ①：個々のオブジェクト属性をオブジェクト名付きJSON化する

オブジェクト属性を拾い出し、JSON化し、オブジェクト名を付与するところまでを「ObjectJsoner」で行っていて、中身は、「ObjectDecomposerOnChanged」と「XYZJSONBuilder」、「XYZWJSONBuilder」、「JSONBuilder」、「ObjectJSONBinder」で構成されています
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/155423/af8927e0-5d9c-997d-c8a3-e74fb51b8e93.png)

### ①－１：オブジェクト属性のJSON化

最初は「ObjectDecomposerOnChanged」ですが、これはオブジェクトのPosition／Rotation／Scaleのいずれかに変化があった場合、Trueを返しつつ、変化があった場合のJSON構築のためのPosition／Rotation／Scaleの値を次に流しています
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/155423/ee9be7a4-88d1-46c0-e1d0-f498c76ced15.png)

JSON構築のためのPosition／Scaleなどの3値は、「Float3UpdateChecker」で、前回変化時とのFloat3の比較を行い、変化していれば、Trueを「ObjectDecomposerOnChanged」に渡します
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/155423/791a972d-3418-dcfd-7aae-35c84687b76c.png)

なおRotationは「FloatQUpdateChecker」で処理していますが、コチラはFloat3では無く、FloatQで比較を行います

### ①－２：オブジェクト属性のJSON化

オブジェクトから取り出したPosition／ScaleなどのXYZ座標値や、RotationのXYZWの4値は、「XYZJsonBuilder」によって、JSON化します
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/155423/3ea647da-d2dd-849c-0f92-eca6c60fa127.png)

「ToJSON」でキー名と各値をJSONキーバリュー形式にします
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/155423/def01be1-59c9-ca76-6faf-56afb5301958.png)

ちなみにPosition／Scaleは3値なので、「XYZJsonBuilder」で処理しますが、Rotationは4値なので、「XYZWJsonBuilder」で処理していますが、要領はXYZJsonBuilderと同じです

### ①－３：JSON化されたオブジェクトPosition／Rotation／Scaleを連結

JSON化されたオブジェクトPosition／Rotation／Scaleは、カンマ区切りで連結する必要があるため、「JSONBuilder」で繋ぎ合わせます（幾つか空欄のテキスト入力を接続していますが、これはデバッグ用なので、気にしないでください）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/155423/94195726-9b5c-ae50-46e0-19ce88896c9d.png)

### ①－４：オブジェクト属性JSONにオブジェクト名を付与

JSON化されたオブジェクトPosition／Rotation／Scaleの上位に、オブジェクト名（オブジェクトのSlotから取得）の付与を「ObjectJSONBinder」で行います
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/155423/469e3f3a-a5f7-4208-7eb6-dbc56e00d3fc.png)

なお、VR |> AR投影アプリでは、オブジェクトのユニーク性を、オブジェクト名で行っている（もっと良いオブジェクトを一意に識別できる情報がNeosVRオブジェクトに存在していたら、ぜひ教えてください）ため、投影したいオブジェクトは、全て名前を変えておく必要があります

## ②：オブジェクト属性群を接続してWebSocket送信

複数のオブジェクト群の属性を1つのJSONに繋ぎ合わせ、WebSocket送信する「ObjectJsonSender」は、「ProjectorJsonBuilder」と「TimingReducer」、「WebSocketSend」で出来ています
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/155423/8ccab6b4-d850-15da-26e2-7c594965a5ce.png)

### ②－１：複数のオブジェクト群の属性を1つのJSONに繋ぎ合わる

「ProjectorJsonBuilder」は、個々のオブジェクト属性を、1つのJSONに繋ぎ合わせ、その上位にくる「id」や「data」キー、それらに関するデリミタを付与し、デバッグしやすくするための改行も付与します
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/155423/7fca2930-4880-6eeb-240d-b4ff3d6a418e.png)

### ②－２：オブジェクト属性群JSONをWebSocket送信

ここまでで、送信するJSONは出来上がったので、最後に、WebSocketでこのJSONを送信する部分です

まずNeosVRでWebSocketを扱うには、何らかのオブジェクトに、「Attach Component」で「WebSocketClient」を追加してあげる必要があります

ここでは、下図右にあるコーン（三角錐）のオブジェクトに、「WebSocketClient」をアタッチしました
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/155423/aadb117b-a7dc-0bd0-91e3-9587347109e9.png)

#### ②－２－１：WebSocket接続、チャンネルjoin

その後、WebSocketClient配下の「IsConnected」にチェックを入れると、このオブジェクトからWebSocketでElixirに接続します
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/155423/453b86f1-78b1-f342-99fb-c1fb1882a6ac.png)

接続後は、「neos:logix」というWebSocketルーム（後述するElixir側でこのルームを構築します）に「phx_join」を投げるというjoin用パーツ「WebSocketSendJoin」を作ります

ここでWebSocket送信のために、「WebSocket Text Message Sender」というノードを使います
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/155423/aadb117b-a7dc-0bd0-91e3-9587347109e9.png)

このパーツが出来上がったら、「Pulse」をクリックすれば、ルームにjoinできます

#### ②－２－２：WebSocket送信

それから、送信するJSONをWebSocket送信するパーツ「WebSocketSend」を作りますが、ここでも「WebSocket Text Message Sender」を使います

なお、WebSocket送信間隔を「TimingReducer」内にある「Timer」で0.1秒間隔で定期送信するようなロジックになっており、FPSを意図的に抑制していますが、上記で「ObjectDecomposerOnChanged」によりオブジェクトのPosition／Rotation／Scaleが変わったときのみTrueを返す部分も作っているため、「オブジェクト変化があったら送信」で繋ぎ変えるとFPSを落とさずに連携するモードになります

もしくは、「0.1秒間隔経過、かつオブジェクト変化があったら送信」というような形にすると、ムダな通信量を削減できます
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/155423/c7d1fdea-3e2f-96d4-6fc4-e53d8e5ac537.png)

ここまででNeosVR側は完了で、オブジェクトの変化がElixir側に送信されるようになります

## Elixir側

Elixir側は、非常にカンタンで、「Channel」という機能を使ってWebSocketサーバを立てて、NeosVRから受け取ったオブジェクト属性群をブロードキャストしているだけです

Phoenix PJ配下に生成済みの下記コードに、コメントアウトを解除するだけで、WebSocketサーバの構築は完了です

```elixir:/lib/basic_web/channels/user_socket.ex
defmodule BasicWeb.UserSocket do
  use Phoenix.Socket

  ## Channels
  # channel "room:*", BasicWeb.RoomChannel
  channel "neos:*", BasicWeb.NeosLogixChannel  # <- radd here
…
```

WebSocket受信時の処理は、下記のようなコードを作成し、handle_in()でWebSocket受信時のアクションを書いてあげればOKです

```elixir:/lib/basic_web/channels/neos_logix_channel.ex
defmodule BasicWeb.NeosLogixChannel do
	use Phoenix.Channel

	def join( "neos:logix", _message, socket ) do
		{ :ok, socket }
	end

	def handle_in( "new_msg", %{ "body" => body }, socket ) do
		broadcast!( socket, "new_msg", %{ body: body } )
		{ :noreply, socket }
	end
end
```

## 参考：VR |> AR投影アプリ

VR |> AR投影アプリは、この中では最もヘビィな造りをしていて、今回の本題から逸れるため、別の機会にシェアしたいと思いますが、概要だけお伝えすると、ARアプリ側で、現実を写すカメラ画像の上に、NeosVRから送られたオブジェクト属性群JSONを元に、ARオブジェクト生成／変更／消滅を行い、重ね合わせを行っているだけです

そのため、Elixirでのデータ加工等は特に行っておらず、NeosVRから受け取ったJSONをそのままElixirがブロードキャストし、それをVR |> AR投影アプリが受け取り、上記AR処理を行っています

# VR |> AR投影アプリでどんなことが可能になるのか？

NeosVR内で作ったオブジェクトやワールドや、NeosVRに取り込んだCADデータを、任意の緯度／軽度に出現させ、それをARグラスやARコンタクトで見ることで、「現実空間へのデジタル建造」が誰にでも出来るようにします

https://twitter.com/piacere_ex/status/1289987396126138368

これを応用して、航空機やドローンで撮影した街や家屋の点群データをNeosVRに取り込み、その土地に住む方も、これから移住を考えている方も、果ては遠く海外からも、NeosVR内で改造した街に実際に行き、街歩きしながらAR投影で自分の作った街を楽しめる … そんな未来の地方創生を、NeosVR＋Elixirで叶えていく活動をしています

https://twitter.com/piacere_ex/status/1284672027484254208

現時点では、VRとARを繋ぐテクノロジーがまだあまり発展していないため、それをイチ早く実現／体験できるNeosVR＋Elixirには、とてつもなく大きな可能性があると見ています

# いま作っているもの

オブジェクト群のPosition／Rotation／Scaleのリアルタイム反映と、テクスチャ／メッシュのプリロード、親子関係を持つオブジェクトの一斉同期までは出来ていて、下記が現在、開発中です

- オブジェクトテクスチャのNeosVRからVR |> AR投影アプリへのリアルタイム反映
 - WebSocket経由の通信でバイナリも投げられるようにする
 - 現時点は、NeosVRとVR |> AR投影アプリの両方にテクスチャを別々にインポートしている
- オブジェクトメッシュのNeosVRからVR |> AR投影アプリへのリアルタイム反映
 - NeosVR側が「MeshX」という形式を使っているので、まず解析から
 - 現時点は、NeosVRとVR |> AR投影アプリの両方にメッシュを別々にインポートしている

MeshXの攻略を一緒にやってくれる方、絶賛募集中です

# 最後に

VR進化の最先端である「NeosVR」と、プログラミング言語進化の最先端である「Elixir」の2つが悪魔合体させると、わりかし気軽に、「VR空間」と「現実世界」を繋げられることが伝われば幸いです

私自身、NeosVRでVRプログラミングに触れ始めて、まだ日も浅いため、NeosVR側の解説は拙いところも多いのですが、こうした活動にご興味あれば、下記の私のTwitter DMでご連絡ください

<b>piacere の Twitter DM（リンク先の赤枠部分をクリックしてください）</b>
https://twitter.com/piacere_ex
[![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/155423/09c171ae-3885-a0b1-fea6-b5e4c4253e48.png)](https://twitter.com/piacere_ex)

# p.s.このコラムが、面白かったり、役に立ったら…

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/155423/67568d54-6584-ad33-77c7-b3b3afe70287.png) や ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/155423/f5b819ef-d655-ef72-294c-b1a9d2c5b563.png) にて、どうぞ応援よろしくお願いします:bow:

---

明日の記事は、@rhenium_vrcさんの[「NeosFesta2を支えるダイナミックスポーンシステム」](https://qiita.com/rhenium_vrc/items/c8933e576039f783ddd8)です