プログラムの整理と拡張(前編)
前章までのスケッチでは様々な情報を個々のグローバル変数として保持しており,また処理をほとんど
すべてdraw
メソッド内に詰め込んでいたため機能拡張が困難である.
本節では,今後の拡張(反射や屈折の実装)のため,前節までのプログラムを整理する.
交差判定の整理
いままでは半直線–球の交差判定を行っていた.
半直線との交差判定の方法は,対象の形状(球,平面,円柱,立方体,etc..)ごとに異なる. しかし,どのような方法であっても交差判定の結果として交点の位置や交点における法線方向が 分かれば陰影の計算が可能である.
そこでこの交差判定の方法を抽象化してしてみよう.
まず形状を表すクラスShape
を定義する.
上記は,オブジェクト指向開発で用いられるクラス図という図である.クラス図はその名の通り, クラスの概要を書き表すための図形表現である.
上記のように,1つのクラスを1つの四角形で表現する.この四角形は縦に3つに区切られており, 上から順に以下のようになっている.
- クラス名を書く部分
- 属性を書く部分
- 操作(メソッド)を書く部分
なお,クラス名を書く部分以外は省略可能である.
上記の例は以下のコードに対応する.
class なんとかクラス { public int hoge; public float piyo; public void doSomething() { /* ..省略.. */ } public char doNothing(){ /* ..省略.. */ } }
testIntersection
メソッドは物体との交差判定を行う抽象メソッドである.引数として半直線を表すクラス
Ray
のインスタンスを受け取り,交点に関する情報を表すクラスIntersetionPoint
を返す.交点がない場合にはnull
を返す.
Ray
とIntersectionPoint
はそれぞれ以下のようなクラスである.
球や平面,円柱などといった具体的な形状を導入するには,Shape
クラスを継承した,
それぞれの形状に対応するクラスを定義してtestIntersection
メソッドをオーバーライドすればよい.
注意:ここの説明は実験内容とは直接的には関係がないため,本当に興味があり,
かつ理解できる自信がある者だけが読むこと.
無闇に読んで頭の中を「???」でパンクさせないように気をつけること.
Cの構造体は一度その中身を定義したら,その定義そのものを変更する以外の方法でメンバー変数を追加するといったことができない. Processing(Java)のクラスでもそれは同様だが,Javaでは既存のクラスを拡張して,属性やメソッドを追加することができる. このことを継承という.
class Hoge // 既存のクラス { public int data1; } class Piyo extends Hoge // Hogeを継承した新しいクラス { public float data2; }
クラス定義の後ろにextends
と書いて,元となるクラスを指定する.このようにして
宣言したクラスは元となったクラスの属性やメソッドを全て含んでいる.
Piyo p = new Piyo(); // Piyoクラスのインスタンスを生成 // Piyoのメンバーを表示してみる println(p.data2); // Piyoで定義された属性はもちろんのこと println(p.data1); // 元となったHogeのメンバーも兼ね備えている!
この元となったクラス(上記の場合Hoge)のことをスーパークラスとか基底クラス,親クラスなど呼ぶ. これに対して,継承したクラス(上記の場合Piyo)のことを,その元となったクラスのサブクラスとか派生クラス, 子クラスなどとよぶ.
ちなみに,クラス図ではクラスの継承関係を,以下のような白抜きの三角形のついた矢印で結んで表現する.
継承では属性を追加するだけでなく,サブクラスではスーパークラスで定義されたメソッドの中身を上書きすることができる. このことをメソッドのオーバーライドという.
class Hoge // 既存のクラス { public void doSomething() { println("Hoge!"); } } class Piyo extends Hoge // Hogeを継承した新しいクラス { public void doSomething() // オーバーライド { println("Piyo!"); } }
上記の例では,Hoge
クラスでdoSomething
というメソッドが定義されているが,,
サブクラスであるPiyo
で同名のメソッドを定義して挙動を上書きしている.
Hoge h = new Hoge(); // Hogeクラスのインスタンスを生成 h.doSomething(); // "Hoge!"と表示される. Piyo p = new Piyo(); // Piyoクラスのインスタンスを生成 p.doSomething(); // "Piyo!"と表示される.
実行結果は以下のようになる.
これの何がうれしいのか?そのことを説明するためにクラスの継承がもつもう一つの性質を説明する必要がある.
そのもう一つの性質とは,スーパークラスとそのサブクラスは代入互換性を持つ,ということである. つまり上記の例ではHogeクラスの変数に,Piyoクラスのインスタンスを代入することができる,ということである(逆は不可).
Hoge h = new Piyo(); // Hogeクラスの変数にPiyoのインスタンスを代入. h.doSomething(); // 何が呼び出される?
さて上記の例ではHoge
クラスの変数を宣言して,そのなかにPiyo
のインスタンスを生成して代入している(1行目).
これはコンパイルエラーなどにはならない.型が食い違っているが,Hoge
とPiyo
は親子関係のクラスとして定義したので,
代入互換性を持つためである.
3行目ではdoSomething
メソッドを呼び出しているが,この呼び出しによって何が起こるだろうか.Hoge
クラスとPiyo
クラスの
定義を再掲する.
class Hoge // 既存のクラス { public void doSomething() { println("Hoge!"); } } class Piyo extends Hoge // Hogeを継承した新しいクラス { public void doSomething() // オーバーライド { println("Piyo!"); } }
Hoge
クラスのdoSomething
メソッドはコンソールに”Hoge!”と表示するメソッドであり,
Piyo
のそれは”Piyo!”と表示するメソッドである.
この呼び出しによってコンソールには”Hoge!”と表示されるのだろうか?
それとも”Piyo!”と表示されるのだろうか?(つづく)
(先に前節を読むこと)
Hoge h = new Piyo(); // Hogeクラスの変数にPiyoのインスタンスを代入. h.doSomething(); // 何が呼び出される?
このコードの実行結果は以下のようになる.
3行目の呼び出しによって呼び出されるのはPiyo
クラスでオーバーライドしたdoSomething
メソッドである.
いっぽう以下の例ではどうなるだろうか?
Hoge h = new Hoge(); // Hogeクラスの変数にHogeのインスタンスを代入. h.doSomething(); // 何が呼び出される?
これは当然のことながらHoge
クラスで定義されたdoSomething
メソッドが呼び出される.
どういうことかというと,メソッドをオーバーライドしている場合, 呼び出されるメソッドはその変数の型に関係なく,その変数が指しているインスタンスの型に依存する,ということである.
最初の例では,変数の型はHoge
だが実際にはPiyo
のインスタンスを指していたし,
二つ目の例ではHoge
のインスタンスを指していた.
このように,同じHoge
型の変数があったとしても,それが実際に何を指しているかによって
そのメソッドの振る舞いが変わる.このことを多態性とか多相性という場合がある.
英語でポリモルフィズムとも言う.
この性質を使うと面白い実装が可能となる.(つづく)
例えば,あなたが三角形や四角形,円といった図形を扱うアプリケーションを書いているとしよう. そのアプリケーションの中で,入力された図形の面積を知りたい場面があったとする.
1. 入力された図形データを受け取る. 2. 図形の面積を計算する. 3. 2で計算した面積をつかって何かする.
一口に図形データと言っても三角形であったり四角形であったり円であったりして,それぞれで計算方法は異なる. なので,処理2で以下のように場合分けすることが考えられるが…
1. 入力された図形データを受け取る. 2. 図形の面積を計算する. if ( 三角形の場合 ) 三角形の面積を計算する. else if ( 四角形の場合 ) 四角形の面積を計算する. else if ( 円の場合 ) 円の面積を計算する. ... 3. 2で計算した面積をつかって何かする.
このコードのまずさが分かるだろうか?図形の形状が三種類程度ならこれでも良さそうだが,図形の種類が増えれば この場合分けも際限なく増えてしまう.
そもそも,この処理では図形の面積が必要なのであって,入力された図形が具体的に何であるのかは知る必要がない. 多態性はこのような場面で役に立つ.
たとえば,以下のような図形のためのクラスFigure
を作って面積を計算するメソッドを用意しておく
class Figure { public float calcArea(){ /* ..省略.. */ } }
calcArea
メソッドが面積を計算するメソッドだとする.
そして,このクラスを継承して三角形,四角形,円に対応したクラスを定義すれば…
// 三角形クラス class Triangle extends Figure { public Point p1, p2, p3; // 頂点の座標 public float calcArea(){ /* 三角形の面積を計算 */ } } // 四角形クラス class Quadrangle extends Figure { public Point p1, p2, p3, p4; // 頂点の座標 public float calcArea(){ /* 四角形の面積を計算 */ } } // 円クラス class Circle extends Figure { public Point center; // 中心 public float radius; // 半径 public float calcArea(){ /* 円の面積を計算 */ } }
このようにしておけば,当初の図形の面積を利用する処理をスマートに実装することができるようになる. リスト1をProcessing(Java)で実装すれば以下のようになるだろう.
// 1. 入力された図形データを受け取る. Figure fig = /* 何らかの図形 */; // 2. 図形の面積を計算する. float area = fig.calcArea(); // 3. 2で計算した面積をつかって何かする. println(area); // 例えば単に表示する.
このコードの中では,Triangle
やCircle
といった先ほど定義したクラスに一切言及していない.
実際の図形が何であるかは変数fig
が指す先のインスタンス次第であるが,それが実際には何のインスタンスであるかに
関わらずこのコードは正常に動作する.また,図形の種類が増えてもこのコードは一切変更する必要が無い.
このことは,このリスト1を「図形の面積を計算する」という抽象的な操作によって実装することができた, と言うことができる.
ところでFigure
クラスをもう一度よく見てみよう.
class Figure { public float calcArea(){ /* ..省略.. */ } }
calcArea
メソッドの中身として仮に/* ..省略.. */
と書かれているがここには何を書くべきだろうか?
答えは「何も書く必要がない」である.
Figure
クラスは,「具体的にはなんだか分からないけど,とにかく図形である」というクラスである.
calcArea
メソッドは,このメソッドでその面積を計算するよ,という取り決めであって中身で何かをする必要はない.
しかし空のメソッドは作れない.このようなときはメソッドのシグネチャにabstract
というキーワードつけて,
「このメソッドは中身がないですよ」とマークする.
class Figure { public abstract float calcArea(); // ブロック'{}'を書く必要が無い. }
このような呼び出し方だけが決まっているメソッドを抽象メソッドという.
ちなみにこのままではエラーとなる.Javaでは抽象メソッドを一つでも持つクラスは,クラスの宣言にも
abstract
キーワードを付ける必要がある.
abstract class Figure { public abstract float calcArea(); }
このようなクラスを抽象クラスという.抽象クラスはインスタンスを作ることができない,という制限がある.
Figure fig = new Figure(); // エラー
抽象クラスに対して,それを継承して抽象メソッドをオーバーライドしたクラスを具象クラスという場合がある.
この例では,先ほど登場したTriangle
クラスやQuadrangle
クラス,Circle
クラスが具象クラスにあたる.
抽象クラスのインスタンスを生成することはできないが,そのクラスが持つ抽象メソッドを全てオーバーライドした 具象クラスのインスタンスなら生成することが可能である.
Figure fig = new Figure(); // これはできないが… fig = new Triangle(); // 三角形型や… fig = new Circle(); // 円型といった具体的な // 図形クラスならインスタンスを作れる.
ポイント
- 元からあるクラスを継承して属性やメソッドを追加した別のクラスを定義することができる.
- 元のクラスをスーパークラスという.
- 継承したクラスをサブクラスという.
- 継承したクラスでは親クラスで定義されたメソッドの中身を書き換えることができる.
- このことをメソッドのオーバーライドという.
- 始めからサブクラスでオーバーライドすることを前提としたメソッドを定義することができる.
- このようなメソッドを抽象メソッドという.
- 抽象メソッドを1つでも含むクラスを抽象クラスという.
- 抽象クラスはそのままではインスタンスを作れないが,抽象クラスが持っている抽象メソッドを 全てオーバーライドしたサブクラスはインスタンス化できる.
例えば以下のように使用する.
// 「中心位置(0,0,5),半径1の球」のオブジェクトを生成 Shape s = new Sphere(new PVector(0,0,5), 1); // 「始点位置(0,0,5),Z正方向の半直線」のオブジェクトを生成 Ray r = new Ray(new PVector(0,0,5), new PVector(0,0,1)); // 上記の球と半直線の交差判定を行う. IntersectionPoint res = s.testIntersection(r); if ( res != null ) // 交点あり { // 表示してみる println(res.position); // 交点位置 println(res.normal); // 交点における法線方向 }
光源の整理
前回までの内容では,点光源のみを扱っていた.
光の広がり方のモデルは,点光源以外にも平行光源やスポットライトなど様々なものがある. ライティング(光の当たり方)の計算方法はそれら光源のタイプごとに異なるが,どのような タイプの光源であっても物体表面における入射ベクトルの方向と入射光の強度が分かれば 続く放射輝度(≒色)の計算が可能である.
そこで先ほどの交差判定の例と同様に,このライティングの計算を抽象化してみよう.
まず,光源を表すクラスLightSource
を定義する.
lightingAt
メソッドは,ライティングの計算を行う抽象メソッドである.
引数としてライティング計算を行う3次元空間中の位置(PVector
)を受け取り,その位置における
ライティングに関する情報を表すクラスLighting
を返す.
Lighting
は以下のようなクラスである.
点光源や平行光源,スポットライトといったモデルの光源を導入するには,LightSource
クラスを
継承したクラスを定義してlightingAt
メソッドをオーバーライドすれば良い.
例えば以下のように使用する.
// 「位置(-5,5,5)にある白色の点光源」のオブジェクトを生成する LightSource ls = new PointLightSource(new PVector(-5,5,5), new FColor(1,1,1)); PVector p = /* 物体との交点 */; // 点pにおけるライティングを計算する. Lighting ltg = ls.lightingAt(p); // 表示してみる. println(ltg.distance); // 光源までの距離 println(ltg.direction); // 入射ベクトル(点pから光源に向かうベクトル) println(ltg.intensity); // 点pにおける光源の強さ
放射輝度の計算の整理
前回までのスケッチでは三つの反射係数$$k_{a}, k_{d}, k_{s}$$を使用した.反射係数(反射率)は入射した光に対する反射する光の比率である.たとえば,当たった光の量が1.0で反射した光の量が0.7であれば反射係数は$$\frac{0.7}{1.0}=0.7$$となる.
現実の物体表面では,反射係数(反射率)は光の波長によって異なる.別の言い方をすれば反射率は光の波長の関数である. たとえば(極端な例であるが)ある物体表面において,波長700nmの光の反射率が1.0で,それ以外の波長の反射率が0.0ならばその物体は赤く見える(図1).
前章の実装では反射係数$$k_{a}, k_{d}, k_{s}$$が登場したが,レイトレーシング法において 物体表面の色を再現するには,これら反射係数をRGB各チャネルごとに定義する必要がある.
また,光源の強度$$I_{i}$$も環境光の強度$$I_{a}$$も単一の値として処理していた.
色の付いた光を再現するには光の強さも同様にRGB各チャネルごとに定義する必要がある.
これらは前回までのプログラムでは(穴埋めソースの通りならば)冒頭の以下の部分に相当する.
// 定数の初期化 PVector eyePos = new PVector(0,0,-5) ; // 視点位置 PVector spherePos = new PVector(0,0, 5) ; // 球の中心位置 float sphereR = 1.0 ; // 球の半径 PVector lightPos = new PVector(-5,5,-5); // 光源位置 float lightIntensity = 1.0 ; // 光源の強度 float ambientIntensity = 0.1 ; // 環境光の強度 float kAmb = 0.01; // 環境光反射係数 float kDif = 0.69; // 拡散反射係数 float kSpe = 0.30; // 鏡面反射係数 float shininess = 8 ; // 光沢度
環境光反射係数(kAmb
),拡散反射係数(kDif
),鏡面反射係数(kSpe
)が「物体の反射係数」,
光源の強度(lightIntensity
),環境光の強度(ambientIntensity
)が「光の強さ」である.
これらは全てfloat
型であるが,カラー化にはこれをRGB3チャンネルに対して定義する必要がある.
たとえば環境光反射係数なら,単一のfloat
値だったものが,Rチャンネルの環境光反射係数,
Gチャンネルの環境光反射係数,Bチャンネルの環境光反射係数,といった具合に3つのfloat
値にする
必要がある.「光の強さ」も同様である.
これら「3つのfloat
値」を表すため以下のようなクラスFColor
を導入する.
見ての通り,red
,green
,blue
という名前のパブリックなfloat
型の属性を有するクラスである.以下のように使用する.
値の設定はset
メソッドかコンストラクタを使用する.
FColor col1 = new FColor(); // 初期化 col1.set(1.0, 0.5, 0.25); // RGB各チャンネルに値を設定.以下と同じ. // col1.red = 1.0; // col1.green = 0.5; // col1.blue = 0.25; // コンストラクタで値の指定も可能 FColor col2 = new FColor(0.3, 0.2, 0.1); // RGB各チャネルを指定して生成 FColor col3 = new FColor(0.1); // RGB全て0.1で生成
PVector
で説明したように,FColor
もクラスであるためやはり意図しない
エイリアスを作ってしまわないように注意が必要である.インスタンスのコピーが必要な場合は,PVector
と同様にcopy
メソッドを用いる.
FColor col4 = col2.copy(); // インスタンスをコピー(PVectorと同様)
FColor
もPVector
と同様にprint
/println
メソッドで(正確には文字列と結合することで),表示が可能である.
println(" color : " + col2); // 表示.以下のように表示される. // color : [ 0.3, 0.2, 0.1 ]
色の同士の計算として加算代入と乗算代入をサポートしている.以下のように使用する.
col1.add(col2); // 加算代入.以下と同じ. // col1.red += col2.red; // col1.green += col2.green; // col1.blue += col2.blue; col1.mult(col3); // 乗算代入.以下と同じ. // col1.red *= col3.red; // col1.green *= col3.green; // col1.blue *= col3.blue;
放射輝度の値としてFColor
を使う場合,放射輝度から色(Processingのcolor型)への変換に注意する必要がある.
この場合FColor
の値はあくまでもレイトレーシングの結果推定された物体表面における放射輝度である.
シミュレーションによって得られる放射輝度の値はいかなる値も取り得るため, いかなる放射輝度の値が32bitカラーのいかなる値に相当するのか,は事前に決めておく必要がある.
toColor
メソッドはこの変換を行うメソッドである.
color c1 = col4.toColor(); // 放射輝度[0.0, 1.0]の範囲を[0, 255]に変換 color c2 = col4.toColor(2.0); // 放射輝度[0.0, 2.0]の範囲を[0, 255]に変換 color c3 = col4.toColor(1.0, 2.0, 3.0); // RGBでマッピングの範囲を変えた変換
また,記述の簡便性のため色の総乗$$\left(\prod{}{}{c_i}\right)$$を計算する
colorPi
というメソッドも定義してある.このメソッドは引数をいくつでも
与えることができる.以下のように使用する.
FColor col4 = colorPi(col1, col2, col3); // 総乗 // 以下と同じ結果となる // // col4.red = col1.red * col2.red * col3.red; // col4.green = col1.green * col2.green * col3.green; // col4.blue = col1.blue * col2.blue * col3.blue;
まとめ
ここまでに登場したクラスをまとめる.
以下は交差判定に関連するクラス群である.
以下は光源と放射輝度計算に関するクラス群である.
以下の課題でここで導入したクラスを使って前節と同じ動作をするスケッチを作成してみよう.
課題
準備
課題2-3,2-4では,本節で導入したクラスライブラリをスケッチに追加する必要がある.以下の手順を実行せよ.
D.1) 新規にスケッチを作成する.
D.2) 空のスケッチを保存する.ファイル
メニューの保存
をクリックする.
D.3) 以下のようなダイアログが表示されるので,HalfRayTracingという名前で保存する.
D.4) タブの隣の下向きの三角形(▼)をクリックし,新規タブ
を選択する.
D.5) ファイル名の入力を求められるので,適当な名前を入力する.ここではGeometryUtilsと入力してOKボタンをクリックする.
D.6) 空のソースファイルがスケッチに追加される.
D.7) 以下のボタンをクリックするとクリップボードに必要なソースコードがコピーされるので,D.6の空のソースファイルにペーストする.
D.8) この状態で一度保存する.
課題2-3. testIntersectionメソッドを実装する
前節でコピー&ペーストしたソースコードでは,Sphere
クラスのtestIntersection
メソッドが
未実装の状態となっている.このメソッドを実装せよ.
以下の手順に従え.
E.1) 以下のボタンをクリックするとtestIntersection
メソッドのテストドライバ(≒テストコード)がクリップボードにコピーされるので,
これをスケッチHalfRayTracing
のメインのタブにペーストする.
テストドライバには一切手を加えてはならない.
E.2) Sphere
クラスのtestIntersection
メソッドは,GeometryUtils
の113行目付近にある.元から書いてある2行を削除してこのメソッドを実装する.
testIntersection
メソッドの仕様は「交差判定の整理」の通りであるが,一応再掲する.
testIntersection
メソッドは物体との交差判定を行う抽象メソッドである.引数として半直線を表すクラスRay
のインスタンスを受け取り,交点に関する情報を表すクラスIntersetionPoint
を返す.交点がない場合にはnull
を返す.
E.3) Sphere
クラスのtestIntersection
メソッドを実装できたら,を押して実行する.
Sphere
クラスのtestIntersection
メソッドを正しく実装できた場合,コンソールに以下のように表示されるはずである.
ヒント
課題2-4. 「最初の陰影処理」の再実装
本節で導入したクラスライブラリを使用して,「課題2-2. 最初の陰影処理(環境光,拡散反射光,鏡面反射光)」を実装せよ.
課題2-3のメインのタブにコピー&ペーストしたテストドライバを全て削除し,改めて実装を始めること.
シーン設定などは「課題2-2」に準じるが, ライティングに関わるパラメータを以下のように変更する.
- 環境光反射係数
- $$k_{a}=(0.01, 0.01, 0.01)$$
- 拡散反射係数
- $$k_{d}=(0.69, 0.00, 0.00)$$
- 鏡面反射係数
- $$k_{s}=(0.30, 0.30, 0.30)$$
- 光沢度
- $$\alpha=8$$
- 環境光の強度
- $$I_{a}=(0.10, 0.10, 0.10)$$
- 直接光の強度
- $$I_{i}=(1.00, 1.00, 1.00)$$
括弧$$()$$で囲まれた3つの実数の組は,それぞれRチャンネル,Gチャンネル,Bチャンネルの反射係数/光の強度を意味する. 拡散反射係数のみG,Bチャンネルの反射係数がゼロとなっている.したがって,シーン中の球は赤く見えるはずである.生成結果は下図のようになる.
ヒント
課題2-5. 複数の物体を表示する
「課題2-4. 『最初の陰影処理』の再実装」を改造して,複数の物体を表示できるように変更せよ.
課題2-4のスケッチをHalfRayTracing_MultiShapes
という名前で別名保存(ファイル
メニューの名前を付けて保存
をクリックして保存)してからとりくむこと.
表示すべき物体は以下である.
- 球1
- 中心$$(3,0,25)$$,半径$$1$$
- 球2
- 中心$$(2,0,20)$$,半径$$1$$
- 球3
- 中心$$(1,0,15)$$,半径$$1$$
- 球4
- 中心$$(0,0,10)$$,半径$$1$$
- 球5
- 中心$$(-1,0,5)$$,半径$$1$$
- 平面
- 法線方向$$(0,1,0)$$, 位置$$(0,-1,0)$$
後述の物体のリストには,物体のデータをこの順序で格納すること.勝手に順序を変更してはならない.
ここで初めて平面が登場するが,このために必要なコードは全て実装済みである.「準備」の節で コピー&ペーストしたクラスライブラリの中にその実装が含まれているため,この課題では単にそれを使えば良い.
「平面」はShape
クラスのサブクラスPlane
クラスとして実装されている.以下のように使用する.
// 「法線方向が(1,1,1)で,原点(0,0,0)を通る平面」のオブジェクトを生成 Plane pln = new Plane(new PVector(1,1,1), new PVector(0,0,0)); // 表示してみる println(pln.normal); // 法線方向 println(pln.position); // この平面が通る点
※ コンストラクタで面法線と面が通る点を指定した場合は,面法線ベクトルは正規化される.
Plane
クラスはShape
クラスのサブクラスであるため,testIntersection
メソッドを備える.
つまり半直線(Ray
クラス)との交差判定が可能である.
Plane pln = /* 何らかの平面 */; Ray ray = /* 何らかの半直線 */; IntersectionPoint intP = pln.testIntersection(ray); if( intP != null ) // 交差あり // ..何か処理.. else // 交差なし // ..何か処理..
(この課題ではShape
クラスの参照を通じてPlane
クラスのインスタンスを操作することが多いため,
初期化処理以外でPlane
クラスを直接操作する必要はない.これはSphere
クラスも同様である.)
複数の物体,つまり複数のShape
クラスのインスタンス(つまり物体のリスト)を保持する方法はいくつかあるが,ここではArrayList<E>
を用いる.
以下のように使用する.
ArrayList<Shape> shapes; // 物体のリスト // ..中略.. void setup() { // ..中略.. shapes = new ArrayList<Shape>(); // 物体のリストを生成 Sphere sph1 = /* 何らかの球オブジェクト */ ; shapes.add(sph1); // 物体のリストに追加 Sphere sph2 = /* 何らかの球オブジェクト */ ; shapes.add(sph2); // 物体のリストに追加 Plane pln1 = /* 何らかの平面オブジェクト */ ; shapes.add(pln1); // 物体のリストに追加 }
(※ Sphere
やPlane
はそれぞれ球や平面を表すクラスである.これらはShape
クラスのサブクラスであるため,Shape
クラスと代入互換性を持つ.そのためShape
のリストに追加することができる.)
リスト内の要素を反復するには以下のようにする.
// リスト内の各要素を処理する for(int i = 0; i < shapes.size(); ++i) { Shape shp = shapes.get(i); // 何かの処理 }//for // 以下でも同じ(こちらの方が簡潔) for(Shape shp : shapes) { // 何かの処理 }//for
生成結果は下図のようになる.
図2. HalfRayTracing_MultiShapesの生成画像
ヒント1
shapes
に入っている全ての物体を反復して,視線レイと交点を持つ物体を探す.
視線レイと交点を持つ物体の中で,放射輝度を計算するのは視点に最も近い物体である.
誤ってshapes
のうち視線レイと交点を持つ最初の物体を放射輝度の計算に使ってしまわないように注意せよ.
shapes
に物体が視線に近い順で格納されているとは限らない.
0 Comments.