Websocketで、異空間通信。その2

# 概要
メタバースを使った、自動運転シュミレーターを提供します。

# 方針を転換する。
今まで、neosvrの世界でセンサーを作り、その情報を外に出して、エージェントが環境として読み込み自己判断させようとしていたが、
# これからは、エージェントも環境も外で作り、表示のみneosvrで表示する。

上記に従い以下を実験する。

エージェント（ローバー）が環境で、自動運転するシュミレータを作り、結果をwebsocketに出力、neosvrで受け取り、ＢＯＸを移動させる。
websocketサーバーは、elixirで準備します。
設定は、ローバーが、８の字運転を続けるシュミレーターです。
各ポイントは、以下です。
var point1 = {
  'x': 260,
  'y': 148
};
var point2 = {
  'x': 278,
  'y': 169
};
var point4 = {
  'x': 161,
  'y': 278
};
var point3 = {
  'x': 141,
  'y': 257
};
ローバーは、ステートマシンを持っていて、ポイントをクリアすれば、次のポイントに向かいます。
ローバーは、getDistanceとgetDegreeでポイントまでの距離と方角を得て、自分で判断します。
環境は、６０ｆｐｓで更新し、ローバーを移動し、位置を更新します。
ローバーは、位置を受け取り、方向を出力します。
さらに、ローバーの位置情報をwebsocketで、垂れ流しします。
neosvrは、ローバーの位置情報をwebsocketで受け取り、ＢＯＸを移動させます。

# 手順

# websocketサーバーの準備

いつものやつです、省略。

# エージェントと環境の準備
Plunkerに設置。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/18104/a764b959-afc1-10f7-673d-099c6858e0d1.png)

# サンプルコード

```


function getDistance(x, y, x2, y2) {
  var distance = Math.sqrt((x2 - x) * (x2 - x) + (y2 - y) * (y2 - y));
  return distance;
}
function getDegree(x, y, x2, y2) {
  var radian = Math.atan2(y - y2, x - x2);
  var degree = radian * 180 / Math.PI + 180;
  return degree;
}
function draw() {
  ctx.save();
  ctx.translate(point1.x, point1.y);
  ctx.fillStyle = "red";
  ctx.fillRect(-5, -5, 5, 5);
  ctx.restore();
  ctx.save();
  ctx.translate(point2.x, point2.y);
  ctx.fillStyle = "red";
  ctx.fillRect(-5, -5, 5, 5);
  ctx.restore();
  ctx.save();
  ctx.translate(point3.x, point3.y);
  ctx.fillStyle = "red";
  ctx.fillRect(-5, -5, 5, 5);
  ctx.restore();
  ctx.save();
  ctx.translate(point4.x, point4.y);
  ctx.fillStyle = "red";
  ctx.fillRect(-5, -5, 5, 5);
  ctx.restore();
  ctx.save();
  ctx.translate(car.x, car.y);
  ctx.fillStyle = "maroon";
  ctx.fillRect(-3, -3, 3, 3);       
  ctx.restore();
}
var koko;
var canvas;
var ctx;
var state = 2;
var r = 0;
var car = {
  'px': 0,
  'py': 0,
  'x': 210,
  'y': 210,
  'r': 0
};
var point1 = {
  'x': 260,
  'y': 148
};
var point2 = {
  'x': 278,
  'y': 169
};
var point4 = {
  'x': 161,
  'y': 278
};
var point3 = {
  'x': 141,
  'y': 257
};
function hand(p) {
  var d = getDistance(car.x, car.y, p.x, p.y);
  if (d < 5) 
  {
    return;
  } 
  var f = getDegree(car.x, car.y, p.x, p.y);
  var e = getDegree(car.px, car.py, car.x, car.y);
  var s = Math.abs(f - e);
  if (s < 5) 
  {
    return;
  }  
  if (s < 180)
  {
    if (e < f)
    {
      r = 10;
    }
    if (e > f)
    {
      r = -10;
    }
  }
  else
  {
    if (e < f)
    {
      r = -10;
    }
    if (e > f)
    {
      r = 10;
    }
  }
}
function send() {
  var mes = "[";
  mes += car.x / 30;
  mes += "; 0.5; ";
  mes += car.y / 30;
  mes += "]";
  koko.value = mes;
  socket.send(mes);
}
function loop2() {
  var dis;
  car.px = car.x;
  car.py = car.y;
	car.r += r * Math.PI / 180;
	car.x += Math.cos(car.r) * 0.3;
	car.y += Math.sin(car.r) * 0.3;
  switch (state)
  {
  case 0:        
    dis = getDistance(point4.x, point4.y, car.x, car.y);
    if (dis < 5)
    {
      state = 1;
      break;
    }
    hand(point4);
  break;
  case 1:
    dis = getDistance(point3.x, point3.y, car.x, car.y);
    if (dis < 5)
    {
      state = 2;
      break;
    }
    hand(point3);
  break;
  case 2:
    dis = getDistance(point2.x, point2.y, car.x, car.y);
    if (dis < 5)
    {
      state = 3;
      break;
    }
    hand(point2);
  break;
  case 3:        
    dis = getDistance(point1.x, point1.y, car.x, car.y);
    if (dis < 5)
    {
      state = 0;
      break;
    }
    hand(point1);
  break;         
  }
}

canvas = document.getElementById('canvas');
koko = document.getElementById('koko');
canvas.width = canvas.height = 300;
ctx = canvas.getContext('2d');
function loop() {
	draw();
  loop2();
  send();
  requestAnimationFrame(loop);
}

let url = "ws://localhost:50002/";
let socket = new WebSocket(url);
socket.onmessage = function(event) {
  //alert(event.data);
};
socket.onclose = event => alert(`Closed`);
socket.onopen = function(event) {
  alert('open');
  loop();
};
socket.onerror = event => alert(`err`);





```

# 成果物

https://embed.plnkr.co/plunk/nZ3hEVZciUfKyssAQxsP

# メタバースの準備

![2024-02-20 21.14.33.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/18104/cb60c5f5-912c-c4f4-9fa3-df7b40c2a3e0.jpeg)

手順
DevToolTipを装着
新規作成>3Dモデル>Boxをクリック
Transform->Interaction->TouchButtonを追加
追加コンポーネント->network->WebsocketClient
logixtooltipを装着
TouchButtonのインターフェースを取り出す 右ドラッグしてRキー
WebsocketClientのインターフェースを取り出す
boxのスロットルを取り出す　インスペクタ左上の「●Box」の文字部分を右ドラッグしてRキー
ノードブラウザを表示
Interaction>ButtonEventsをスポーン
network->websocket->WebsocketConnectをスポーン
network->websocket->WebsocketTextMessageReceiverをスポーン
Users>HostUserをスポーン
actions->writeをスポーン
variables->stringをスポーン
Operation>Parse>ParserFloat3をスポーン
下の様に、俺コードでつなぐ。


```
{w0, _, _, _, _, _, _, _, _, _} = ButtonEvents [Obj.Box.TouchButton]
{} = WebsocketConnect [w0, FrooxEngine.WebSocketClient, "ws://localhost:50002/", HostUser]
{w2, w3} = WebsocketTextMessageReceiver [w0, FrooxEngine.WebSocketClient]
{w4} = Write [w2, w3]
{w5} = String [w4]
{w6} = ParserFloat3 [w5]
{} = Obj.Box.Slot.Position [w6]
```

# 起動
websocketサーバーを起動
クライアントを起動
neosvrを起動

# 動作確認
メタバース上のBOXが動けば、成功です。


以上。