前回の第16回「3次元空間で座標を回す」は、星形の3次元座標をy軸で水平に回し、2次元平面に透視投影したうえでアニメーションにした(第16回コード2「星形の3次元座標に遠近法を加えてy軸で回すアニメーション」)。この表現はもうこのまま変えない。今回は、スクリプトの書き方に磨きをかける。具体的には、簡単なクラスを定義してみよう。
透視投影のメソッドをオブジェクトに定める
オブジェクトをクラスからつくると、プロパティとして備わったデータをそのオブジェクトのメソッドで操作できる(IT用語辞典「クラス」参照)。今回のお題で、3次元空間座標をObjectインスタンスでつくった。これをクラスとして定義し直し、透視投影座標はそのメソッドで求めることにする。3次元空間座標のクラス(Point3D)と透視投影のメソッド(getProjetedPoint())が定められた暁には、3次元座標のオブジェクトをつくって、2次元座標に透視投影するステートメントはつぎのようになる。
もっとも、オブジェクトを参照してメソッドを呼ぶには、クラスが必ずしも定義されていなくてもよい。まず、こちらから先に進めよう。第16回コード2は、Ticker.tickイベントのリスナー関数(rotate())から、別の関数(getProjetedPoint())の引数に焦点距離(focalLength)と3次元座標のオブジェクト(point)を与えて、2次元座標に透視投影した。そのステートメントは、つぎのようにオブジェクトを参照した同じ名前のメソッド(getProjetedPoint())の呼出しに書き替える。
つぎに手を加えるのは、3次元座標のオブジェクトをつくる関数(newPoint3D())だ。つくったオブジェクト(point3D)にプロパティのかたちでメソッド名(getProjetedPoint)を与え、定義した関数(getProjetedPoint())の参照を代入する。これで、このプロパティをオブジェクトのメソッドとして呼び出せる。
そして、オブジェクトのメソッドに定めた関数(getProjetedPoint())をつぎのように書き直す。3次元座標のオブジェクトにメソッドとして定めたのだから、引数にオブジェクト(_point3D)は受け取らなくてよい。オブジェクトのプロパティ(getProjetedPoint)に関数が与えられ、メソッドとして呼び出すとき、その関数内ではオブジェクトをthisキーワードで参照できる。したがって、引数のオブジェクトを参照していた記述は、すべてthisに書き替える。
これで、透視投影の関数は3次元座標のオブジェクトにメソッド(getProjetedPoint())として備わった。書き上がったスクリプトは、コード1のとおりだ。ワイヤーフレームの星形がy軸で水平に回るアニメーションは、第16回コード2と変わらない(図1)。マウスポインタの水平位置に応じて、星形の回る向きと速さが変わる。
コンストラクタ関数を定める
つぎは、コンストラクタ関数を定義して呼び出す。それ自体はとても簡単だ。ただ関数を定めて、new演算子で呼び出しさえすればよい。
コンストラクタ関数には値を返すreturnステートメントは書かない。new演算子で呼び出せば、新たにつくられたインスタンスが返る決まりだ。今回は、引数に渡したxyz座標をプロパティとして定めたい。そのときは、コンストラクタ内のthis参照にプロパティを与えればよい。コンストラクタ内でつくられるインスタンスは、thisで参照されるからだ。
すると、前掲コード1の3次元座標オブジェクトをつくって返す関数(newPoint3D())は、つぎのようにコンストラクタ関数として書き替えられる。なお、クラス名(Point3D)はそれらしく変えてみた。
3次元座標をつくって配列に納める関数(createStarPoints())からは、前掲のコンストラクタ関数(Point3D())をnew演算子で呼出すように書き替える。
これで取りあえずクラス(Point3D)は定義でき、クラスにメソッド(getProjetedPoint())も備わった。試してみれば、星形はマウスポインタの水平位置に合わせてy軸で回る。ただし、クラスへのメソッドの定め方が標準的ではない。
コンストラクタのプロトタイプオブジェクトにメソッドを定める
前項のクラスへのメソッドの定め方には、ふたつ問題がある。ひとつは、メソッドを本格的に加え始めると、コンストラクタの中がメソッド設定の羅列で見づらくなる。もうひとつは、メソッドに与えた関数が、メソッドでなく直に関数としても呼べてしまうことだ[1]。関数が直に呼ばれると、その中のthis参照はグローバルオブジェクトになってしまうので正しく動かない。
そこで、クラスに備えるメソッドは、コンストラクタ関数のFunction.prototypeプロパティに定める。このプロパティはコンストラクタとして呼出された関数に備わるプロトタイプオブジェクトを参照する。コンストラクタからつくられたインスタンスを参照すると、コンストラクタのthis参照などをとおしてインスタンスに与えられたプロパティやメソッドにアクセスできる。けれど、その中に見当たらないときは、コンストラクタのプロトタイプオブジェクトを探し始め、見つかればインスタンスに定められたのと同じに扱えるのだ。
コンストラクタのFunction.prototypeプロパティにメソッドを定めるときは、名前のない関数で与える。そうすれば、直に関数を呼ばれるおそれがなくなるからだ。
そこで、前項で定義したコンストラクタ(Point3D())とメソッド(getProjetedPoint())を、つぎのように書き直す。コンストラクタ関数のFunction.prototypeプロパティに定められた関数(getProjetedPoint())は、すべてのインスタンスから参照でき、自身のメソッドとして呼び出せる。
メソッド(getProjetedPoint())をひとつだけもつクラス(Point3D)がこれで定義できた。ここでひとつ、大切な注意がある。メソッドは名前のない関数を代入して定めている。そのため、このコンストラクタを呼び出すステートメントは、代入文の後に書かなければメソッドが備わらない。つまり、クラス定義はJavaScriptコードの冒頭に置くのがよい。そうしてまとめたスクリプト全体がつぎのコード2だ。
このお題は、これででき上がりとする。jsdo.itにコードを掲げた。次回はお題を変えて、クラス定義と透視投影にもう少し取り組んでみたい。このようなサンプルを予定している(なお、このサンプルにはインタラクションはない)。