MMDダンスをM5Core2のディスプレイに表示する


以前、以下の投稿をしています。
　[「原神」がMMDモデルを配信してくれたので、JavascriptでMMDビューアを作った](https://qiita.com/poruruba/items/35085f3ab3db3fa17d2d)

今回は、ブラウザ上でMMDダンスしている様子を、ESP32のM5Core2のディスプレイに表示してみます。
ESP32でのディスプレイ表示は、Lovyanさんの以下のライブラリを利用させていただいていますので、いろんなESP32デバイスで表示できるかと思います。

lovyan03/LovyanGFX

https://github.com/lovyan03/LovyanGFX

M5Core2とブラウザとの間は、WebSocketで通信しています。

Links2004/arduinoWebSockets

https://github.com/Links2004/arduinoWebSockets

もろもろのソースコードは、GitHubに上げておきました。

poruruba/MMDViewer_WebSocket

https://github.com/poruruba/MMDViewer_WebSocket

#M5Core2側

WebSocketを開いておいて、受信したJpegをLovyanGFXでLCDに描画しているだけです。

1点だけ、注意がありまして、WebSocketのデフォルトのバッファサイズが小さく、あふれるとコネクションが切断されてしまうので、画像ファイルのサイズに合わせて、大きくしています。
今回は、解像度が320x240なので、25KB程度にします。

```h:MMDViewer_WebSocket\.pio\libdeps\m5stack-fire\WebSockets\src\WebSockets.h
#define WEBSOCKETS_MAX_DATA_SIZE (25 * 1024)
```

main.cppのソースコードです。

```cpp:MMDViewer_WebSocket\src\main.cpp
#define LGFX_AUTODETECT // 自動認識

#include <M5Core2.h>
#include <LovyanGFX.hpp>
#include <WiFi.h>
#include <HTTPClient.h>
#include <WebSocketsServer.h>

const char* wifi_ssid = "【WiFiアクセスポイントのSSID】";
const char* wifi_password = "【WiFiアクセスポイントのパスワード】";

#define WEBSOCKET_PORT  81 // WebSocketのポート番号

WebSocketsServer webSocket = WebSocketsServer(WEBSOCKET_PORT);
static LGFX lcd; 

void wifi_connect(const char *ssid, const char *password){
  Serial.println("");
  Serial.print("WiFi Connenting");
  lcd.println("WiFi Connecting");
  
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
    Serial.print(".");
    lcd.print(".");
    delay(1000);
  }
  Serial.println("");
  Serial.print("Connected : ");
  Serial.print(WiFi.localIP());
  Serial.print(" (");
  Serial.print(WEBSOCKET_PORT);
  Serial.println(")");
  lcd.println("");
  lcd.print("Connected : ");
  lcd.print(WiFi.localIP());
  lcd.print(" (");
  lcd.print(WEBSOCKET_PORT);
  lcd.println(")");
}

void webSocketEvent(uint8_t num, WStype_t type, uint8_t * payload, size_t length) {
  switch(type) {
    case WStype_DISCONNECTED:
      Serial.printf("[%u] Disconnected!\n", num);
      break;
    case WStype_CONNECTED:{
        IPAddress ip = webSocket.remoteIP(num);
        Serial.printf("[%u] Connected from %d.%d.%d.%d url: %s\n", num, ip[0], ip[1], ip[2], ip[3], payload);
      }
      break;
    case WStype_BIN:
//      Serial.printf("WStype_BIN : [%u] get binary length: %u\n", num, length);
      lcd.drawJpg(payload, length);
      break;
    case WStype_ERROR:
      Serial.printf("WStype_ERROR : [%u]\n", num);
      break;
    case WStype_FRAGMENT_BIN_START:
    case WStype_FRAGMENT:
    case WStype_FRAGMENT_FIN:
    case WStype_FRAGMENT_TEXT_START:
    default:
      break;
  }
}

void setup() {
  Serial.begin(9600);
  Serial.println("setup");

  lcd.init();

  wifi_connect(wifi_ssid, wifi_password);

  webSocket.begin();
  webSocket.onEvent(webSocketEvent);
}

void loop() {
  webSocket.loop();
}
```

環境に合わせて、以下の部分を変えてください。
・【WiFiアクセスポイントのSSID】
・【WiFiアクセスポイントのパスワード】


#ブラウザ側

MMDファイルのJavascriptによる表示は以下で投稿しています。
　[「原神」がMMDモデルを配信してくれたので、JavascriptでMMDビューアを作った](https://qiita.com/poruruba/items/35085f3ab3db3fa17d2d)

まずは、上記を参考にして、three.jsとMMDローダをセットアップしておきます。
また、お好きなMMDのPMD/PMXファイルをダウンロードし配備しておきます。（著作権の関係で置いていません）。
(お好みでダンスのためのVMDファイルやステージのためのPMDや、ポーズのためのPMXファイルも)
配置したフォルダやファイル名を以下に記載しておきます。

```js:public_html\js\start.js
var vmd_list = [
・・・
var stage_list = [
・・・
var pmx_list = [
・・・
```

あとは、HTML Canvasへの描画タイミングに合わせて、CanvasからJPEGを作成し、WebSocketでM5Core2に送信しています。

以下の部分です。
あまりに早く送信するとESP32側のWebSocketの通信バッファがあふれてしまうようで、間隔をあけて送信しています。

```js:public_html\js\start.js
        this.selecting.mmd.setCallback((canvas) =>{
            if( socket.bufferedAmount <= 0 ){
                var end_tick = new Date().getTime();
                if( this.start_tick == 0 || ((end_tick - this.start_tick) >= this.duration)){
                    this.start_tick = end_tick;
                    canvas.toBlob((blob) =>{
                        socket.send(blob);
                    }, "image/jpeg", this.quality / 100);
                }
            }
        });
```

以下の部分は環境に合わせて書き換えてください。
・【M5Core2のIPアドレス】

# 実行方法

ブラウザからページを開き、websocket_urlにM5Core2のIPアドレスを指定して、PMXファイルのキャラクタ名を選択します。
 
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/261826/567b7a7f-5f00-cc31-9dbb-cf4d01e4e06b.png)

そうすると、M5Core2に踊る画像が表示されます。更新間隔を200msec以上じゃないとうまく表示されませんでした。LovyanGFXの問題ではなく、WebSocketの問題です。

（保護シート付けたままなので、汚くてすみません！）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/261826/48828b90-c4c7-b8bc-a0cd-a7affc35ee8d.png)

ちなみに、ブラウザのCanvas上で、マウス操作することで拡大や視点移動ができ、その結果もM5Core2の画面に反映されるのがわかります。

#補足

ESP32内に立ち上げられるWebSocketサーバはHTTP接続のため、HTTPSで立ち上げたWebページのJavascriptからは「Mixed Content」となって接続できません。
その場合は、Web Proxyサーバを立ち上げる必要があります。以下を参考にしてください。

　[HTTPで立ち上げたWebSocketサーバに、HTTPSで接続するためのWeb Proxyの立ち上げ](https://qiita.com/poruruba/items/d0f5ef3ac4d09cfad593)

以上