スマホでも動く物理演算で転がる3Dサイコロを作ったのでその解説

カテゴリ:3D 2013年9月 1日 16:46

CSS3DRendererはexamplesの中

ルービックキューブの解説は少しお休みして、Three.jsのCSS3DRendererを使ってサイコロを作った話をしようと思います。といっても、ルービックキューブを作る上で使用した概念などもあるので、まったくの無関係というわけではありません。

今回のポイントは「CSS3DRenderer」です。
これ、実はThree.jsだけを読み込んでも使用できません。実験段階だからなのか、Three.jsのgithubのリポジトリ内にある「examples」の中にこっそり含まれています。

実装サンプル

なので、まずはこれを取り出してThree.jsと一緒に読み込ませます。
ちなみに今回作成したものは以下のようなサイコロです。(CANNON.jsという軽量の物理エンジンも使用しています)

見てもらうと分かりますが、こうした基本的な形状であればCSSでも問題なく表示することが出来ます。
また、Three.jsでオブジェクトを生成したりテクスチャを貼ったり、という処理すらなく、単純にDOMを生成してそれを立体に仕上げるだけです。
つまり、このデモはiPhoneでも問題なく見れます。

立体にするための回転や移動のマトリクス的な処理は全部Three.jsがやってくれるので、直感的に形を作っていくことができます。(例:dice.rotation.x = THREE.Math.degToRad(45))

コード解説

ポイントを絞って今回のコードの解説をしていこうと思います。
まずはなにはともあれ、CSSで表示できるようにするまでを見てみます。

ソースコード

各面の情報の定義

順を追って見ていきます。まず最初はサイコロの各面用の情報を用意しています。position、rotation、テクスチャURLの3つです。
cubeSizeはキューブのサイズを別の場所で定義しています。サイコロのサイズですね。
cubeSizeは位置情報として利用しているので、実際のサイズは2倍にする必要があります。(中心点からの移動量なので、左にcubeSize、右にcubeSize分移動します。そのため、各面のサイズは2倍になります)

空のCSS3DObjectをdiceの親として定義

そして情報の定義の下に見慣れた記述があります。そう、まずは普通にdiv要素を作って定義されたキューブのサイズにサイズを指定します。
このdivがサイコロの各面を内包する要素となります。
生成したあとはTHREE.CSS3DObjectに、先ほど生成したdiv要素を指定してインスタンスを作ります(関連付けます)。
こうすることでThree.jsの3Dの変換などがこのdiv要素に適用されるようになる、というわけです。

各面を生成

最後に、サイコロの各面を生成します。boxInfoに6面分の情報が入っているのでそれをfor文で回し、各面を生成します。
生成の仕方はdiv要素のときとまったく同じです。DOMを生成し、それをTHREE.CSS3DObjectに関連付けるだけです。
生成後、boxInfoで定義された位置と回転情報を適用します。(face.position.fromArray(info.position)) そして生成された各面をdiceに追加します。(dice.add(face);

サイコロの生成はこれだけです。
あとはここで生成したdiceをThree.jsのsceneオブジェクトにaddし、rendererでレンダリングしてあげればサイコロが出現します。
Three.jsの使い方はここでは長くなってしまうので割愛します。使い方については「THREE.js で WebGL>」に詳しく書かれているのでこちらを参考にするといいと思います。
SceneやCamera、Rendererの生成の仕方はまったく一緒です。(ただし、今回の目的でもあるCSSでレンダリングさせるために使用するレンダラーは「CSS3DRenderer」を利用する必要があります)

サイコロのどの面が上かを判定する

実はここが一番書きたくてこの記事を書いていますw
今回はCANNON.jsというのを使って物理演算させていますが、これの使い方についてはまた別の機会にしたいと思います。(まだそこまでちゃんと使ってないし)

さて、どの面が上を向いているかどう判別したらいいでしょうか。人間であれば見たまま一目瞭然ですね。
しかし、コンピュータはそれを数値で表さないとなりません。なので、なんらかの計算をした上で、その数値から判断する必要があります。

では、どうしたらそれを判別できるか。まずは「なにがどうなっていたらそれは上を向いていると見なせるか」を考えてみます。
下の画像は、サイコロと座標系の軸を表示したものです。(最近ハマっているので、無駄にBlenderで作りましたw)
サイコロを置いて、XYZ軸の向きを表示したもの。Y軸正の向きに1、X軸正の向きに3が向いている
画像を見ると「1」が上を向いていますね。そして軸の情報を見てみると、「Y軸の正の向き」が上向きと一致しています。

では次の画像はどうでしょうか。今度はサイコロが回転して、X軸の正の向きが上を向いています。
上記画像のサイコロが回転し、今度は3とX軸正の向きが画面上を向いている状態
どうやらどの軸のどの向きが上を向いているか、が分かれば上を向いている面が判別できそうです。
しかし、サイコロはCANNON.jsの物理演算によってぐるぐるとたくさん回転してしまっています。実際それが今どうなっているかはどうしたら分かるでしょうか。

Three.jsはワールド空間座標での変換行列を持っている

実はThree.jsのCSS3DObjectは(もちろん、WebGLとして作る場合のObject3Dオブジェクトも)、このワールド空間座標でどの位置にどんな向きでモデルが配置されているか、という情報を持っています。
それはmatrixWorldというプロパティに保存されています。
この変換行列を適用すると、とある点がワールド空間上でどの位置にあるか、が分かります。
つまり、この行列を使えば現時点のワールド空間座標系で軸や頂点がどういう向きになっているかが計算できるというわけです。

X軸正の向きで試してみる

実際にやってみましょう。試しにX軸正の向きがどちらを向いているかやってみます。
jsdo.itに上がっているサンプルで、「2」が上向きの状態で試してみます。

まずX軸の正の向きを表すベクトルを作ります。ベクトルを作るには、今回はTHREE.Vector4クラスを使います。THREE.Vector4クラスにはapplyMatrix4というメソッドがあり、自身のベクトルに行列を適用することができるようになっています。
実際にベクトルを生成している部分は以下です。

このベクトルに対して行列を適用すると結果は「x: -0.00044029735727235675, y: -0.9999998807907104, z: 0.00009558126475894824, w: 0」となりました。この値が意味するところはなんでしょうか。
これは、行列適用後X軸正の向きがワールド空間座標系でどの方向を向いているか、というベクトル情報です。

本来なら「1」が向いている方向となりますが、ものすごーくわずかに真上からずれた方向を向いているようです。

実はプログラムで数値を計算する以上、どうしても限界があります。細かい数値はある程度切り捨てられます。無限の桁数では計算できませんからね。
なので、数回の計算を行ううちに、この切り捨てられた数値部分の誤差がどうしても出てしまいます。
それが、この「-0.9999998807907104」という数値になって現れたのです。

とはいえ、他の数値に目を向けてみると明らかに値が違います。ここは誤差を許容して0.99以上、とすることで判別ができそうです。

その判定を行っているのが「if (px.applyMatrix4(dice.matrixWorld).y > UP) {...}」の部分です。(UPは0.99を変数に入れているだけです)
この「y」は上で説明した通り、計算後のX軸正の向きの現状の向きです。つまり、yの値が-0.99ということはマイナス値、すなわち下を向いている、ということになります。

すべての軸・方向についてチェックする

これを、各軸各方向すべてに対して確認することで、どの面が上をむいているかを判別しています。
実際のソースは以下です。

実際のソースを見てみると、各軸の各方向について計6種類のベクトルを生成し、それに行列を適用させてその結果の判定を行っています。
もしyの値が0.99以上の値を持つものが見つかったら、それを面の上の結果として判断しその面に描かれているサイコロの目を出力する、という流れです。
どの軸の方向がどの数字か、は作ったサイコロによるのでそこは事前に決めておかないとなりません。
そこがしっかりと定義できていれば、あとは判別した軸と合わせて該当する数字を使うだけです。

まとめ

いかがだったでしょうか。
実際にスマホで見てみても、PCとまったく遜色なく動くと思います。(特にiPhoneは)
CSSで問題なく作れる形状に関してはスマホでも動くものが比較的簡単に作れます。
CSSということは表示しているものはDOMそのものなので、画像を表示するもコンテンツを表示するも自由自在です。

CSS3DRendererはだいぶ夢があるなぁと思いました。(まぁ、iPhoneでWebGLが動いてくれるようになれば話しは早いんですけどね・・)
コンテンツにちょっとした驚きを追加するにはとてもいい選択だと思うので、ぜひ色々試してみてください。

さて、次回はまたルービックキューブの話に戻ろうと思います。(ちなみに今回のこの軸の方向がどちらを向いているか、というのはルービックキューブの制作でも使用しました)

この記事のカテゴリー一覧を見る⇒3D

  • このエントリーをはてなブックマークに追加

トラックバックURL

http://css-eblog.com/cgi-bin/mt/mt-tb.cgi/217