はじめに
過去の記事で簡易的な3D表現をJavaScriptで実現する方法を紹介しました。理屈の紹介と、単に立方体が回転するだけでは退屈なので拡張してルービックキューブのGUIを作成します。
完成品はこのように動きます。
ルービックキューブGUIを作成する際の課題
ルービックキューブのGUIを作成する際、解決しなければならない課題がいくつかあります。
- 構成するオブジェクトが多い
- レンダリングが複雑
- パーツの移動の表現が複雑
このくらいでしょうか。それぞれについての解説と、採用した解決方法を紹介します。
構成するオブジェクトが多い
ルービックキューブはシンプルなパズルではありますが、構成部品が多く、26個の立方体で構成されています。26個の立方体を全て6面体として扱うと156個の面を持ち、管理しなければならない頂点の数は208個にも及びます。ここに回転の表現が加わると、行列積を用いた場合は、頂点1か所あたり33回の乗算、24回の加減算が行われます。
※四元数を用いる方法で計算の回数を少し減らすことができます。
コンパイル言語の場合はこの程度の計算量は問題になりませんが、スクリプト言語の中でも、特に算術演算が遅い言語を選ぶとこの計算量が目に見えるようになります。ですが、今回使用しているJavaScriptは算術演算が非常に高速なため、計算負荷としては問題になりません。
構成部品が多い特徴は計算機的な問題点もありますが、全く別の問題も含んでいます。とてもシンプルで、コーディングが辛い、という問題があります。理論的にはシンプルでも、作らなければならない物量が多いため、出来上がるソースコードの大半は定義行になります。この類の作業はどうしても時間がかかるので、時間の都合でこういったアプリケーションが作れない方は多いと思います。
レンダリングが複雑(1)
ルービックキューブの構成部品を26個の立方体として実装すると、全てを6面体として扱うと156個の面があることは先述の通りです。しかし、実際に目に見える面は、高々表面の27面、何か操作を加えている最中であれば高々28面しか見えません。条件によっては9面しか見えない場合もあります。この、面が見えるか見えないか、の判定でレンダリングすべき面を選定する処理はかなり複雑になります。しかし、今回はこの複雑な処理を考える必要はありません。JavaScriptが高速な言語だからです。カメラから遠い順に全ての構成パーツをレンダリングしてしまえば帳尻が合います。表面を透明色で面の辺だけをレンダリングするとこうなります。
なかなかすごい景色です。レンダリングが遅い言語でこのような雑なアルゴリズムを採用するとフレームレートが一気に落ちます。高速なJavaScriptであっても、処理が若干重くなっているのが筆者の環境では体感できました。ですが、そんなに高いフレームレートでレンダリングすることを目的とはしていなかったため、この問題は扱わないことにしました。
レンダリングが複雑(2)
レンダリングの課題はもう一つあります。キューブに対して何か操作を行っている最中は、キューブが凸多面体ではなくなり、カメラから遠い順にレンダリングする、という横着が破綻します。
このように表示が乱れる現象が発生します。ですが、解決方法はシンプルで、9個の立方体を含む直方体、17個の立方体を含む直方体、の2つの直方体として考え、どちらの直方体が先にレンダリングされるか、を判定してレンダリングすれば上記の表示の乱れを回避することができます。
パーツの移動の表現が複雑
ルービックキューブに対して回転動作を作用させると、構成部品が物理的に移動します。青〇で印をつけたキューブが
ここにある場合と、
ここにある場合とで、作用させることが可能な動作が変化します。この変化を実装するのは大変です。この課題に正面からアタックすると大変ですが、横着できます。大事なのは、見た目が動いたように見えていれば良い、ということです。今回採用した方法は、色だけ入れ替えて座標を変えないという方法です。こうすれば、パーツが移動してしまうことはないので、作用させせる動作の定義も少なくて済みます。汎用3Dエンジンで実装しようとした際、
このキューブがここにあるときは、xxxとyyyとzzzの動作ができて...ここにあるときはaaaとbbbとcccの操作ができて...
のようなことをやり始めると考慮するパターンの多さにうんざりします。
まとめ
今回はルービックキューブのGUIを作成する際に生じる課題と解決方法を紹介しました。大きな課題を正面突破しようとすると非常にコストがかかる場合でも、横着して辻褄を合わせることができることは多々あります。今回の題材はルービックキューブでしたが、実際の業務でも似たような現象によく遭遇します。課題に対する解決策は様々であり、どの方法が最適かは状況に依存します。