プログラミングの動画を作ろうと思って
試しに作ってみたのが前回のワイヤーフレームのデモ
でした。今回はソースコード解説となります。
sample3d.zip (3,997 bytes)
ZIP圧縮してあります。展開するとパッケージinfo.nagise.sample3dに以下の5つのjavaファイルがあります。
なお、ソースの文字コードはUTF-8となっています。
| Line.java |
29行 |
613 bytes |
直線を表すクラス |
| Matrix1x3.java |
41行 |
915 bytes |
1行3列の行列を表すクラス |
| Matrix3x3.java |
65行 |
1,518 bytes |
3行3列の行列を表すクラス |
| Sample3D.java |
129行 |
3,308 bytes |
起動用のApplet本体 |
| Vertex.java |
62行 |
1,488 bytes |
頂点を表すクラス |
| 合計 |
326行 |
7,842 bytes |
|
動画用に書いたソースにコメントなどを足して整形してあります。
この分量で約40分かかりました。早送りしないでプログラミング動画を作る場合、
10分程度の尺におさめようとすれば100行以下の行数である必要があるのではないでしょうか。
座標計算
座標の計算には数学の行列という概念を利用しています。Matrix1x3および、Matrix3x3が該当します。
wikipediaによると行列の教育課程は
高等学校での数学Cでの範囲ですから、理系のコースを取らない人はそもそも習わないことが多いのでしょうね。
(x, y, z)で表される3次元空間での座標は、1行3列(もしくは3行1列)の行列で表現します。この座標に対し、
回転などの変換を3行3列の行列で表現することが出来、行列の掛け算で座標変換を行うことができます。
今回は3次元空間での回転に用いていますが、2次元空間での平面の変形に用いた例が
アフィン変換です。
Javaの標準APIでアフィン変換はサポートされていますが、javadocを見ると行列での変換が説明されています。
数学に詳しくない人でも、行列の掛け算で座標変換(回転とか変形とか平行移動とか)ができるんだ、
と覚えておくといざ調べるときに役に立ちます。(そんなことを思い立つかは甚だ疑問ですけどね:-)
数学の行列というと身構えてしまいがちですが、プログラムとは相性がよい。
アルゴリズム的に手順を踏んで計算することが容易
なのでポリゴンなどでの作業計算にはよく用いられます。以下に行列の掛け算を行っている箇所のソースを掲載します。
public Matrix3x3 mul(Matrix3x3 val) {
Matrix3x3 ret = new Matrix3x3();
for (int y=0; y) {
for (int x=0; x) {
for (int i=0; i) {
ret.value[y][x] += this.value[y][i] * val.value[i][x];
}
}
}
return ret;
}
いかがでしょうか。非常に機械的な手順で計算できることがわかるのではないでしょうか。
頂点の扱い方
座標は行列によって表現していますが、頂点を表すクラスは別途存在します。
これは、変換前の座標と、変換後の座標を持ちたいためです。
今回はワイヤーフレームですが、グローシェーディングとかやる場合は頂点の向きとか
さらに情報を足さないといけないので、座標 = 頂点という単純な作りにすると嵌ります。
非オブジェクト指向式にやるのであれば、元になるモデルの座標のListと、
変換後の座標のListを用意してやることになりますね。ソースを短く簡単に済ませたいならばそのほうが楽かもしれません。
座標計算のための行列クラスは出来ていますが、適切なパラメータを与えないと回転はできません。以下が回転・平行移動の処理部分。
public void calculate(Matrix1x3 offset,
double rx, double ry, double rz) {
// X軸の回転
Matrix3x3 mx = new Matrix3x3(
1, 0, 0,
0, Math.cos(rx), -Math.sin(rx),
0, Math.sin(rx), Math.cos(rx));
// y軸の回転
Matrix3x3 my = new Matrix3x3(
Math.cos(ry), 0, Math.sin(ry),
0, 1, 0,
-Math.sin(ry), 0, Math.cos(ry));
// z軸の回転
Matrix3x3 mz = new Matrix3x3(
Math.cos(rz), -Math.sin(rz), 0,
Math.sin(rz), Math.cos(rz), 0,
0, 0, 1);
// 行列の合成
Matrix3x3 m33 = mx.mul(my).mul(mz);
Matrix1x3 m13 = m33.mul(this.point);
// 平行移動
this.mapping = m13.add(offset);
}
sinやcosといった三角関数が並んでいますが、回転変換の際の行列は定型で決まっていますので
カンペを用意して計算式をそのまま記載すればOKです。
このように数式をプログラムに落とすのが簡単なのが行列のよいところ。
描画ルーチン
今回はAppletのswing実装であるJAppletクラスを使用しています。
このJAppletにJPanelをaddして、実際の描画はJPanelに行わせます。
/**
* 描画用のPanel
*/
class ExPanel extends JPanel {
Matrix1x3 offset = new Matrix1x3(160, 120, 0);
// 回転角
double rx;
double ry;
double rz;
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
for (Vertex v : vlist) {
v.calculate(this.offset, this.rx, this.ry, this.rz);
v.paint(g);
}
for (Line l : llist) {
l.paint(g);
}
}
};
(0, 0, 0)を中心とした座標で計算していますから、画面の中央が(0, 0, 0)となるようにオフセットを与えています。
また、現在のモデルの回転角をここに持たせています。
実際の描画はVertexクラスとLineクラスに委譲しますので、ここではループをまわすだけ。
/**
* Appletの開始時に呼び出される
* @see java.applet.Applet#start()
*/
@Override
public void start() {
// アニメーション用スレッドの作成
this.t = new Thread(){
@Override
public void run() {
// 割り込みされるまでループ
while(!this.isInterrupted()) {
panel.rx += 0.05;
panel.ry += 0.07;
panel.rz += 0.01;
panel.repaint();
try {
Thread.sleep(20);
} catch (InterruptedException e) {
this.interrupt();
}
}
}
};
this.t.setDaemon(true);
this.t.start();
}
/**
* Appletの停止時に呼ばれる
* @see java.applet.Applet#stop()
*/
@Override
public void stop() {
// アニメーションスレッドに割り込んで停止させる
this.t.interrupt();
}
Applet#start()をオーバーライドしてアニメーション用のThreadを生成して起動します。
Applet#stop()の際にはThreadが停止するようにThread#interrupt()で割り込みを行います。
世の中のサンプルコードでは簡単のためかThread内でのループをwhile(true)としているケースが多いですが、
作法的には本ソースのようにwhile(!this.isInterrupted())とするほうがよいでしょう。
こうすることで、Thread#sleep()がthrowするInterruptedExceptionの処理が自然に記述できますし、
また、Applet#stop()で行っているように、Threadの外部から停止させる処理が自然に記述できます。
このThreadでは適当に回転させて再描画を行うだけですが、座標変換は描画ルーチン内ではなく、
このThreadで行うべきだったなぁと設計を反省。
終わりに
プログラミングの動画をとる場合、早送りナシで10分程度の尺に納めるには100行程度のプログラムではないと厳しいということが分かりました。
こういったビジュアル的にインパクトのあるデモのほうが受けはよいと思ったのですが、色気を出しすぎましたね。
3Dのプログラムをやりたいという人はこういった自力で回転するようなサンプルを書くとよい勉強になります。
今時はAPIが回転とか面倒くさいところはやってくれるのですが、基礎を疎かにすると後で必ず嵌ることになります。
私の場合は学生の頃に「ライブラリを使わずに自力でポリゴンを書けるなら雇ってあげる」とゲーム業界の人に言われ、
ポリゴンの基礎を独習したのですが、実際のところこういった基礎ができるということは大きな売り文句になりますよ。
投稿日時 : 2007年9月29日 10:59