(これはGrimoirejsアドベントカレンダー6日目の記事です。)
みなさん、GrimoireJS使ってますか?
ご存知の通り、GrimoireJSはweb3Dをとてもかんたんに扱うための便利なフレームワークです。
内部に独自レンダラ実装を持つくらいにはゴリゴリとwebglを使っているので、さぞ中身は難解なレンダリングパイプラインや解読不能なglslで満ち満ちているような気がしますね。実際webglは激しい環境依存に深い闇を垣間見るのですが、実はGrimoireJSの基本構造にwebglのコードは1行すらも含まれていません。
GrimoireJSにはGomlNodeとComponentの組み合わせで構成されるプラグインの仕組みが用意されていて、webglを含むコードは公式のものでさえこの上に乗るプラグインに過ぎないからです。
この仕組みによってGrimoireJSは高い拡張性と、用途に合わせた柔軟性を実現していて、拡張機能を追加もとても簡単にできます。
で、そもそもGomlNodeとかComponentってなんなの?
ということで、今回はこのあたりの構造の話をしていきたいと思います。
GomlNode
grimoireJSを使うときはかなり高頻度で使う.goml
ですが、たとえばこんな感じです。(公式チュートリアル参照)
<goml width="fit" height="fit">
<geometry name="cylinder" type="cylinder" />
<scene>
<camera class="camera" near="0.01" far="40.0" aspect="1.5" fovy="45d" position="0,0,20">
<camera.components>
<MouseCameraControl/>
</camera.components>
</camera>
<mesh geometry="cylinder" position="0,0,0" scale="3,3,3" color="#6a5acd" targetBuffer="wireframe"/>
</scene>
</goml>
GrimoireJSはこのgoml
をパースしてツリー構造として扱います。
GomlNodeはこのツリーのノードとなるオブジェクトです。
ノードは名前ごとにいくつかの属性を持っていて、それを指定することで動作を変化させることができます。
つまり、goml
、geometry
、scene
、camera
、mesh
などがここで登場しているGomlNodeですね。
基本的にgomlのタグとそのまま対応しているので、とくに難しくないと思います。
ただし、全てのタグがノードなわけではありません。
Componentも混ざってます。どこで見分けるんでしょうか?
Component
先ほどのコードをよく見ると、
<camera class="camera" near="0.01" far="40.0" aspect="1.5" fovy="45d" position="0,0,20">
<camera.components>
<MouseCameraControl/>
</camera.components>
</camera>
という部分がありますね。
<camera.components>
タグは特別で、GomlNodeではありません。
名前的にもそのままですが、camera
ノードにコンポーネントを追加するための記法です。
gomlのタグの中に、
[ノード名].components
という名の子要素を書き、その中にコンポーネント名をタグとして書くことで、ノードにコンポーネントを追加していくことができます。
このようにコンポーネントを追加していくことで、どんどん機能を拡張していけるわけですね!
で、結局何者なの?
何者なのか説明するといっておきながらまだ使い方しか説明してないのは僕の文章力が足りなかったからです。ごめんなさい。
とりあえず、自作のComponentやノードをプラグインとして追加するときのコードみて見ましょう(公式チュートリアル参照)
gr.registerComponent('Rotate', {
attributes: {
speed: {
defaultValue: '1',
converter: 'Number',
},
},
$mount: function() {
this.phi = 0;
},
$update: function() {
this.phi += this.getValue('speed');
this.node.setAttribute('rotation', this.phi + ',' + this.phi + ',' + this.phi);
},
});
gr.registerNode("rotate", ["Rotate"], {}, "mesh");
<goml width="fit" height="fit">
<scene>
<camera class="camera" near="0.01" far="40.0" aspect="1.5" fovy="45d" position="0,0,20"/>
<rotate geometry="cube" position="0,0,0" color="#0000FF" speed="1" />
</scene>
</goml>
見やすいように若干変更してますがほぼそのままです。
実行してみるとキューブが回転してますね。
index.goml
でrotate
タグを使ってますが、このコンポーネントはindex.js
でgr.registerNode()
によってあたらしく定義されたノードです。その直前ではRotate
コンポーネントも定義しています。
APIの詳細はここを見ていただくとして、やってることはざっくりいって
mesh
ノードにRotate
コンポーネントを追加した、rotate
という新しいノードの定義です。
これによってgomlでrotate
タグが利用できるようになります。
実は、GrimoireJSの全てのノードとコンポーネントは、このサンプルと同じように定義されています。
rotate
はmesh
を拡張していますが、mesh
も同様に他のノードの拡張であり、最後は空のノードにたどり着きます。
空のノードにはツリーの節点である以上の機能はないので、結局ノードの持つ機能は、すべてコンポーネントに実装されているということがわかります。
GrimoireJS-Core
ところで、GrimoireJSを利用するときには大抵、まず公式からバンドリング済みの.jsをダウンロードしてきてhtmlに埋め込むと思います。公式ページに載っているチュートリアルのとおりですね。
これだけでhtml上の.gomlファイルを埋め込んだ位置にcanvasが生成され、gomlで定義された通りの3d空間がレンダリングされます。簡単ですね!
簡単すぎて感動するのですが、
これはGrimoireJS本体の機能ではありません。
実はGrimoireJSの本体はGrimoireJS-Coreと呼ばれ、ざっくり書くと
- gomlをパースしてGomlNodeツリーを構成
- GomlNodeが保持するComponentの管理
- Component間のメッセージの伝達
などという構造的な基盤としての機能しか持っていません。
レンダリングなどの基本的な機能の実装は、grimoirejs-fundamentalというプラグインに分離されています。
ということで、GrimoireJSのプラグイン機能は強力です。大抵なんでもできます。
まとめ
コンポーネントは、モジュール化した機能の実装
ノードは、コンポーネントを入れる箱
という感じでしょうかね?
機能を追加したいときは、
- コンポーネントとして実装して、
- 必要であればノードしてタグを登録
しましょう。あとはgomlを書くだけ!
次回はGrimoireJSのインタフェースの使い方について書く予定です!