MML (Metaverse Markup Language) を試してみた

インターネットをご覧の皆さん こんばんは。
先日公開された MML (Metaverse Markup Language) を触ってみたのでメモがてら記事を書いてみます。

https://mml.io/

試した時点の環境は

- mml v0.1.1

です。

# MML (Metaverse Markup Language) とは？
Improbable社 (https://improbable.io) が主導する、M² (https://www.msquared.io/) というメタバース エコシステムで使用されている技術です。

メタバースは大きく分けると、空間/オブジェクト/人で構成されていますが、
M² は異なるメタバース間でそれらを相互に運用できるようにすることを目標に開発されているようです。

https://codex.msquared.io/overview/introduction

https://codex.msquared.io/overview/m2-network

MML (Metaverse Markup Language) は、そのうちのオブジェクトを記述するオープンソースの技術で、
3Dモデル（メッシュ）やアニメーションだけでなく、メタバース内で動作する際に必要な、意味付けや振る舞いの記述ができるようです。

https://mml.io/

https://codex.msquared.io/technology/mml-stack

# MML の構成要素
## MML ドキュメント
MML では HTML と JavaScript を使用してオブジェクトを記述できます。
いくつかのサンプルが公開されていますが、例えば、クリックすると転がるサイコロの場合、下記のような MML ドキュメントとして記述できるようです。
https://mml.io/examples?example=Dice
```html:dice.html
<m-model
  id="dice"
  src="https://public.mml.io/dice.glb"
  sx="1" sy="1" sz="1"
  y="1"
  rx="0" ry="0" rz="0"
  onclick="rollDice()"
></m-model>
<script>
  function lerp(start, end, t) { return start * (1 - t) + end * t; }
  function radToDeg(radians) { return radians * (180 / Math.PI); }

  let rollResult = 1;
  function rollDice() {
    const rollMap = {
      1: {
        rx: radToDeg(2 * Math.PI),
        ry: 0,
        rz: radToDeg(2 * Math.PI),
      },
      2: {
        rx: radToDeg(2 * Math.PI),
        ry: 0,
        rz: radToDeg(2 * Math.PI - Math.PI / 2),
      },
      3: {
        rx: radToDeg(2 * Math.PI - Math.PI / 2),
        ry: 0,
        rz: radToDeg(2 * Math.PI),
      },
      4: {
        rx: radToDeg(2 * Math.PI + Math.PI / 2),
        ry: 0,
        rz: radToDeg(2 * Math.PI),
      },
      5: {
        rx: radToDeg(2 * Math.PI),
        ry: 0,
        rz: radToDeg(2 * Math.PI + Math.PI / 2),
      },
      6: {
        rx: radToDeg(2 * Math.PI + Math.PI),
        ry: 0,
        rz: radToDeg(2 * Math.PI),
      },
    };

    const diceElement = document.getElementById("dice");

    let newRoll = Math.floor(Math.random() * 6) + 1;
    while (newRoll === rollResult) {
      newRoll = Math.floor(Math.random() * 6) + 1;
    }
    rollResult = newRoll;

    const targetRotation = rollMap[rollResult];
    const startRotation = {
      rx: parseFloat(diceElement.getAttribute("rx")),
      ry: parseFloat(diceElement.getAttribute("ry")),
      rz: parseFloat(diceElement.getAttribute("rz")),
    };
    const animationTime = 400;
    const interval = 40;
    let currentTime = 0;

    let intervalId = setInterval(() => {
      currentTime += interval;
      if (currentTime < animationTime) {
        let t = currentTime / animationTime;
        let currentRotation = {
          rx: lerp(startRotation.rx, targetRotation.rx, t),
          ry: lerp(startRotation.ry, targetRotation.ry, t),
          rz: lerp(startRotation.rz, targetRotation.rz, t),
        };
        diceElement.setAttribute("rx", currentRotation.rx.toString());
        diceElement.setAttribute("ry", currentRotation.ry.toString());
        diceElement.setAttribute("rz", currentRotation.rz.toString());
        diceElement.setAttribute("y", Math.cos(t * 2.0 - 0.5) * 3.5);
      } else {
        diceElement.setAttribute("rx", targetRotation.rx.toString());
        diceElement.setAttribute("ry", targetRotation.ry.toString());
        diceElement.setAttribute("rz", targetRotation.rz.toString());
        clearInterval(intervalId);
      }
    }, interval);
  }
</script>
```

## ネットワークDOM
メタバース空間上ではオブジェクトの状態を全ユーザで共有する必要があります。
MML では、ネットワークDOMという仕組みでオブジェクトの状態を同期しているようです。
MML ドキュメントから構築されたネットワークDOMは、メタバース空間とは独立したサーバーで実行され、ネットワークDOMクライアントと WebSocket で通信することで、ネットワークDOMの要素を操作することができるようです。

https://github.com/mml-io/mml#architecture-diagram
```mermaid
graph TD
      subgraph Networked DOM Server
          networked-dom-server[Networked DOM Server]
          networked-dom-document[Networked DOM Document]
          jsdom[JSDOM]
          websocket[Websocket]
          networked-dom-server --> networked-dom-document
          networked-dom-document --> jsdom
      end
      mml-source["MML Source Code (HTML)"]
      mml-source --> networked-dom-document
    subgraph Web Browser
        web-browser[Web Browser]
        subgraph MML Client
            subgraph Networked DOM Client
                networked-dom-client[Networked DOM Client]
                dom["DOM (HTML)"]
                networked-dom-client --> dom
            end
            subgraph MML Client
                web-browser-mml-client[MML Client]
                three-js[MML THREE.js]
                three-js -- Events --> web-browser-mml-client
                web-browser-mml-client --> three-js
            end
            dom -- Rendered By --> three-js
        end
    end
    networked-dom-client --> ws-addr
    ws-addr --> websocket
    ws-addr["ws://..."]
    websocket --> networked-dom-server
    web-browser --> web-browser-mml-client 
    web-browser-mml-client --> networked-dom-client
```

# MML Playground
MML を試す環境として、mml-playground というリポジトリが公開されています。
こちらを利用すると、自身で作成した MML ドキュメントを手軽に試してみることができます。

https://github.com/mml-io/mml-playground

## MML Playground を CodeSandbox で実行してみる
MML Playground は CodeSandbox.io で実行することができるようになっています。

### サンドボックスで MML Playground を起動する
1. まず、CodeSandbox ( https://codesandbox.io/ ) にサインインします。
1. MML Playground ( https://github.com/mml-io/mml-playground ) に記載されているリンクから、サンドボックスを開きます。
1. 「Please click the 'Fork' button to create your sandbox.」という表示が出る場合は、CodeSandbox 右上の Fork ボタンでサンドボックス環境を作成します。
1. サンドボックス環境が作成されると MML Playground が起動します。

### MML Playground 上に MML ドキュメントを表示してみる
1. MML Playground が起動すると、キャラクターが表示されます。キャラクターはキーボードの WASDキーで移動できます。
1. フィールド上のスロット（緑色の領域）をマウスクリックすると、MMLドキュメントのURLを入力するダイアログが表示されます。
1. MML Playground にはサンプルの MML ドキュメントが含まれており、packages/server/examples/以下に配置されています。<br>MML ドキュメントの WebSocket URL は ```wss://ホスト/examples/ファイル``` となっています。
1. 試しに duck.html を表示する場合は ```wss://サンドボックス環境の識別子.csb.app/examples/duck.html``` のような URL を入力すると、スロット上に duck が表示されます。
1. 独自の MML ドキュメントを作成する場合は、packages/server/examples/以下にファイルを配置すれば同様の方法で表示することができます。
```html:cube.html クリックで色が変わるキューブ
<m-cube id="test-cube" y="0.5" color="red" collide="true"></m-cube>
<script>
  const clickableCube = document.getElementById("test-cube");
  clickableCube.addEventListener("click", () => {
    clickableCube.setAttribute("color", `#${Math.floor(Math.random() * 16777215).toString(16)}`);
  });
</script>
```
```html:WebSocket URL
wss://サンドボックス環境の識別子.csb.app/examples/cube.html
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/191218/016c2f95-4eb8-0475-f3e9-1d9cebd61be7.png)



# おわりに

各種サンプルやサンドボックス環境が用意されていたので、手軽に MML を体験することができました。
HTML と javascript で記述できるため敷居も低い印象です。
まだ公開されたばかりなので、これからできることも増えていくのではないかと思います。

ちなみに、M²（or Morpheus?）はメタバースの空間（場所）を作成するための World Builder も公開準備しているようで、こちらも面白そうだなと思いました。

https://codex.msquared.io/technology/m2-morpheus-platform/the-world-builder

以上 おつきあいいただきありがとうございました。