VOYAGE GROUP VR室ブログ

VOYAGE GROUP VR室のブログです。コンテンツの紹介や制作方法、イベントレポートなどについて書きます。原則毎週水曜日更新。

【何が出るかな】aframe-physics-systemを使いWeb VRで物理演算をやってみる

こんにちは。

最近の推し漫画はジャンプで連載中の鬼滅の刃jujunjun110です。基本的に展開はシリアスでよくできているし、その中に突如ぶっこまれるシュールギャグがまた良い。まだ4巻しか出ていないのでぜひ。

さて、先日VRをやっている方を集めた飲み会を開催しまして、参加者の皆様から事前に集めた話題を選ぶために、勉強も兼ねてWebVRフレームワークA-Frameで簡単なサイコロWebアプリを作成したので、その技術について書いてみます。

https://media.giphy.com/media/26xBAzY5FDQfxiTgA/giphy.gif

↑こんな感じで、おはようからおやすみまで暮らしを見つめてくれそうなテイストのアプリケーションです。

目次

物理演算コンポーネントaframe-physics-system

まず、A-Frameの基本機能にはまだ物理演算の機能がないので、外部コンポーネントを利用する必要があります。

https://cloud.githubusercontent.com/assets/1848368/19297499/806d059a-9013-11e6-9b20-c03294acbc4c.png

今回利用するaframe-physics-systemは、javascriptの物理計算エンジンである CANON.js のA-Frame向けラッパーです。*1

A-FrameはTHREE.jsの上に構築されていますが、もともとTHREE.jsとCANNON.jsの相性がいいのでうまく統合できているということのようです。

インストール

まずはnpmでインストールします。

npm install aframe-physics-system

browserifyなどでjsをまとめる際はただrequireするだけではなく、以下のようにregisterAll()というメソッドを叩く必要があるので注意しましょう。

require('aframe');
require('html2canvas');
require('aframe-html-shader');

var physics = require('aframe-physics-system');
physics.registerAll();

基本的な使い方

使い方は非常にシンプル。

sceneにphysicsコンポーネントを物理演算の対象にしたいオブジェクトにstatic-bodyコンポーネントもしくはdynamic-bodyコンポーネントを付加して使います。

  • static-body ... 物理演算の対象になるが、それ自身は移動しないオブジェクト。壁や床など。
  • dynamic-body ... 物理演算の対象になり、それ自身が移動するオブジェクト。今回の場合はサイコロ。

ただ立方体を真下に落とすだけのシンプルな実装はこんな感じ。

<a-scene physics="debug: false">
        <!-- 物理演算用サイコロオブジェクト -->
    <a-entity 
        dynamic-body="mass:100;linearDamping: 0.01;angularDamping: 0.0001;"  
        geometry="primitive: box" 
        position="0 10 -10" 
    ></a-entity>
    
    <!-- 床 -->
    <a-entity static-body geometry="primitive: box; width: 300; depth: 300; height: 1" position="0 0 0" material="src:floor.jpg; repeat: 20 20;"></a-entity>
<a-scene>

https://media.giphy.com/media/l3q2NBhNCnYOpfKVy/source.gif

飛び跳ねてますね。物理演算できてますね。

physicsdynamic-bodyのパラメータで重力、反発係数、回転しやすさなどを決めることができます。アプリケーションが完成してきたら微調整してみましょう。

サイコロをころがす

次にさいころに力を与えて投げられるようにしましょう。

function throwSai() {

    var sai = document.querySelector('#sai-physics').body;

   // サイコロが力の影響を(再び)受けるように設定する
    sai.wakeUp();

    // サイコロを初期ポジションにセット
    sai.position = new CANNON.Vec3(0, 10, -10);

    // サイコロ全体にZ方向の力を与える
    sai.velocity.set(0, 0, -4);

    // サイコロにランダムな回転を与えて面白い転がり方にする
    sai.applyImpulse(
        new CANNON.Vec3(Math.random() * 20, Math.random() * 20, Math.random() * 20), // 与えるベクトル
        new CANNON.Vec3(0.5, Math.random() * 10, Math.random() * 10) // 力を与える点
    );
}

ここでは、CANNON.js のメソッドであるvelocity.set(x, y, z)applyImpulse(impulse, world_position)などを直接叩いて力を与えています。

aframe-physics-system はCANNON.jsの全てのメソッドをラッピングしているわけではないので、より高度なことをやりたくなったら、el.bodyという名前で取得できるCANNON.jsオブジェクトに対して、CANNON.jsのメソッドを直接叩くことになります。

サイコロの面を追加する

立方体を投げることができるようになったら、各面にテキストを表示していきましょう。

日本語を表示するために、a-boxの子要素としてa-planeを追加し、それぞれにHTMLシェーダーを当てはめていきます。

↓ 詳細は以下の記事が詳しいのでこちらもご覧ください。

vr-lab.voyagegroup.com

えいっ!

https://media.giphy.com/media/26xBCkd90VIqLdsAg/giphy.gif

すり抜けました...😱

おそらくですが、aframe-physics-systemが物理演算をするためにA-Frameオブジェクトのメッシュを利用する際、オブジェクトに子要素があると正しく単一のメッシュが取得できず、結果的に衝突判定などがうまく行われないのでないかと思います。

これはかなり困ったんですが、今回は物理演算用のサイコロと表示用のサイコロを分けることでなんとか解決できました。

f:id:jujunjun110:20170120214642j:plain

子要素があるオブジェクトに物理演算が効かないなら子要素がないオブジェクトで物理演算を行い、子要素ありのオブジェクトに回転と座標をコピーしつづければ擬似的に物理演算できるという発想ですね。

(ちなみに書いてて思ったのですが、明示的にmeshの形状を指定してあげればこんな面倒なことしなくてもうまく動くかもしれないです。)

<!-- 物理演算用サイコロ -->
<a-entity id="sai-physics" 
    pos-tracer="target: sai-display" 
    dynamic-body="mass:100;linearDamping: 0.01;angularDamping: 0.0001;" 
    geometry="primitive: box" 
    position="0 10 -10" 
    material="opacity:0;"
></a-entity>

<!-- 表示用サイコロ -->
<a-entity id="sai-display" geometry="primitive: box" position="0 10 -3">
    <a-entity geometry="primitive: plane" material="shader:html; target: #target1; fps:1" position="   0  0.5    0" rotation="270   0 0"></a-entity>
    <a-entity geometry="primitive: plane" material="shader:html; target: #target2; fps:1" position="   0 -0.5    0" rotation=" 90   0 0"></a-entity>
    <a-entity geometry="primitive: plane" material="shader:html; target: #target3; fps:1" position="   0    0 -0.5" rotation="  0 180 0"></a-entity>
    <a-entity geometry="primitive: plane" material="shader:html; target: #target4; fps:1" position="   0    0  0.5" rotation="  0   0 0"></a-entity>
    <a-entity geometry="primitive: plane" material="shader:html; target: #target5; fps:1" position="-0.5    0    0" rotation="  0 270 0"></a-entity>
    <a-entity geometry="primitive: plane" material="shader:html; target: #target6; fps:1" position=" 0.5    0    0" rotation="  0  90 0"></a-entity>
</a-entity>
AFRAME.registerComponent('pos-tracer', {
    init: function() {
        this.targetEl = document.getElementById(this.data.target);
    },
    tick: function() {
        // ラジアンから度数への変換係数
        var radToDeg = 180 / Math.PI;

        var rot = AFRAME.utils.coordinates.stringify({
            x: this.el.object3D.rotation._x * radToDeg, 
            y: this.el.object3D.rotation._y * radToDeg, 
            z: this.el.object3D.rotation._z * radToDeg
        });

        this.targetEl.setAttribute("position", this.el.object3D.position);
        this.targetEl.setAttribute("rotation", rot);
    }
});

今回はpos-tracerというコンポーネントを書きました。コンポーネントのtickイベントは毎フレーム発火するので、ここで座標と回転をコピーしています。

いい感じに動きました!

https://media.giphy.com/media/26xBAzY5FDQfxiTgA/giphy.gif

あとは、表示するキーワードをランダムにしたり、もう一回サイコロを振れるボタンをつけたり、カメラをズームしたりできるようにしたら完成です!

(↑ボタンを押したりぐりぐりしたりできます)

フルスクリーンでページを閲覧する

githubでソースコードを見る

まとめとお知らせ

というわけで、A-Frameの物理演算の記事を書いてみました。

A-Frame、基本機能に取り込んでもいいような機能であっても外部コンポーネントを使わないと実現できないようなことが多くあります。

おそらく公式としては、各種コンポーネントを公式機能としてサポートすることよりも、便利な機能を外部ディベロッパーが追加・利用しやすいエコシステムを作る方に労力を割き、便利な機能自体は外部ディベロッパーにどんどん開発していってもらおうという思想なのだと思います。

A-Frame 0.4.0から追加されたA-Frame-Registry もまさにそんな感じですね。

一方で、そのミニマルな作りのため、A-Frameの公式サイトを見ただけではUnity等と比較してできないことが多いと思われがちな側面もあるような気がしています。(A-Frameを触り始めたばかりのときの僕がそうでした。)

これは結構かなりもったいないと思うので、今週末開催されるVR Tech Tokyo #5 というイベントで、「ここまできた!2017年 Web VRでできること・できないこと」というタイトルでLTをしてきます!(現在資料の進捗ゼロ😱)

vrtokyo.connpass.com

その際の模様はまたこのブログに書きたいと思いますので、お楽しみに!

次回はVR合コンの開催をもくろむDayBySayが、それを支える技術について書く予定です!

それでは皆様、ごきげんよう。

*1:A-Frame開発者のKevin氏が作ったaframe-physics-componentというのもありますが、そちらは若干物理演算の挙動が怪しいのでこちらのほうがオススメです。