2017-05-18 第6回目 グラフィックス
本日のテーマ
グラフィックス
EZ.java
本日のプログラムを実行するには EZ.java
が必要です.
これから書くプログラムと,同じディレクトリにこのEZ.java
を置いてください.
EZ.java
は正式には,EZ Graphics と呼びます.
このプログラムは,ハワイ大学マノア校のAdvanced Visualization and Applications研究室 Dylan Kobayashi によって開発されたソフトウェアです.
1つのファイルを同じディレクトリに置くことで図形の描画が容易に扱えるようになります.
公式サイトからもダウンロードできますが,
この授業向けに少し修正していますので,EZ.java
をダウンロードして利用してください.
楕円の描画
ダウンロードしたら,早速プログラムを書いていきましょう. 書き終えたら,コンパイル,実行してみましょう. 実行結果を確認できれば,値を変更して再度,コンパイル,実行してみましょう. どこを変更すると,どう変わるのかを確認してください.
import java.awt.Color;
public class DrawOval{
void run(){
EZ.initialize(400, 400); // 画面の大きさを決める.
// 円を描く.(中心座標x, y, 幅,高さ,色,塗りつぶし)
EZCircle circle1 = EZ.addCircle(100, 100, 200, 200, Color.BLUE, true);
EZCircle circle2 = EZ.addCircle(200, 200, 200, 200, Color.RED, false);
}
// mainメソッドは省略.
}
なお,Color
は色を表す型です.この型を利用するときには,import java.awt.Color;
の一文がクラス宣言の前に必要です.
そして,BLACK
, BLUE
, CYAN
, DARK_GRAY
, GRAY
, GREEN
, LIGHT_GRAY
, MAGENTA
, ORANGE
, PINK
, RED
, WHITE
, YELLOW
の13色が定義済みです.
座標系
座標は左右方向がx軸,上下方向がy軸になっており,右側がx軸のプラス方向,左端がx軸の0です. また,上下方向では,一番上がy方向の0,下方向がプラスになっていることに注意してください.
例題1. 楕円の描画の変更
- 円の位置をずらしてください.
- 円の色を変更してください.
- 円の大きさを変更してください.
その他の図形の描画
円以外の図形も描いてみましょう. どのようなメソッドを呼び出せば良いかは,EZ Graphicsのドキュメント を読んでみましょう.
http://www2.hawaii.edu/~dylank/ics111/doc/
各ページの左のフレームは,EZ Graphics が持つ型を表しています. 右のフレームは,その型が持つ情報を表しています.右のフレームを下にスクロールしていくと, メソッドの一覧が閲覧できます(上の右側の画像).
この形式のドキュメントを一般に API ドキュメント と呼びます. Javadocコメントで生成されるドキュメントです.
例題2. 楕円以外の図形の描画
では,EZ GraphicsのAPIドキュメントを参照し, 楕円以外の図形も描いてみましょう.
まず,DrawOval.java
をコピーしてDrawShapes.java
を作成してください.
そして,DrawShapes.java
に追加・変更していってください.
なお,DrawShapes.java
のmain
メソッドの中身も忘れずに変更しましょう.
例題3. アニメーション
アニメーションは基本的にパラパラ漫画と同じ原理で行います.
EZ Graphics では,EZ.addCircle
やEZ.addLine
などで
追加して返される実体の位置を変更し,EZ.refreshScreen()
を呼び出すと画面を更新します.
次の例で確認してみましょう.
import java.awt.Color;
public class RoundTrip{
void run(){
EZ.initialize(400, 400);
EZCircle circle = EZ.addCircle(100, 100, 5, 5, Color.BLUE, true);
this.roundTrip(circle);
}
void roundTrip(EZCircle circle){
Integer deltaX = 10;
while(true){ // 無限ループ
Integer newX = circle.getXCenter() + deltaX;
circle.translateTo(newX, circle.getYCenter());
if(newX >= 400 || newX <= 0){
deltaX = deltaX * -1;
}
EZ.refreshScreen();
Thread.sleep(100);
}
}
}
Javaで一定時間スリープするのは,Thread.sleep
メソッドを利用します. sleep
メソッドに
スリープする時間(ミリ秒)を渡します.
上記の例では,100ミリ秒(0.1秒)スリープしています.
これでコンパイルすると,次のようなコンパイルエラーが発生します.
prog/spring06/RoundTrip.java:17: エラー: 例外InterruptedExceptionは報告されません。スローするには、捕捉または宣言する必要があります
Thread.sleep(100);
^
エラー1個
例外機構(Exception Architecture)
例外(Exception)は,近年のプログラミング言語で採用されている実行時エラーの通知機構です.
従来のプログラミング言語,例えば,C言語の場合,fopen
で開くファイルが見つからなかった場合,
返り値をNULL
にすることでエラーを通知していました. この場合,プログラマが責任を持って,
返り値の値を確認して,正常か異常かを判断しなければいけませんでした.
一方の例外機構は,異常処理を行うための別の処理経路を作るものです. もし,プログラムの実行途中で何らかの異常が発生した時,それまでに行なっていた処理を中断し, 別の処理を行うようにする機構です.
例外が投げられれば,何らかの異常が発生したと判断できるようになります. 逆に,例外が投げられなければ,データが変であろうが,正常な処理であると判断できます (もちろん,開発途中で変な場合はバグの可能性はありますが).
プログラマが明示的に投げる例外も存在しますが,多くの場合,システムが異常を検知し例外を投げます. 例えば,ファイルが見つからない場合,スリープ中に割り込みが入った場合などです. そのような例外が発生した時に,どのような対応をするのかをあらかじめ決めておく必要があります.
検査例外と非検査例外
Javaの例外は,検査例外と非検査例外の2つに分類できます. 違いは次に挙げる通りです.
- 検査例外
- 例外が発生した時の処理をプログラム中に明示的に書いておかなければコンパイルエラーになる例外.
- 代表的な検査例外
IOException
,InterruptedException
など.
- 非検査例外
- 例外が発生した時の処理は書かなくてもコンパイルが通る例外.
- 代表的な非検査例外
NullPointerException
,ArrayIndexOutOfBoundsException
,NumberFormatException
- 非検査例外は,事前にチェックすることで,例外の発生を抑えられます.
- 非検査例外が投げられた場合は,事前のチェックが不十分であるとも言えます.
InterruptedException の責任転嫁
さて,コンパイルエラーで,InterruptedException
が投げられる可能性があると述べられています.
この例外は,スリープ中に割り込みが発生した時に発生する例外です.
この例外が発生した時の対処法をプログラム中に書いておく必要があります.
取れる対処法は2つです.
- 例外が投げられたら,その場で例外に対応する.
- 例外が発生する可能性のあるメソッドを呼び出している元に対応を任せる.
どちらがふさわしいかは場合により異なります.ここでは, 呼び出し元に対応を任せましょう.以下のような対応になります.
Thread.sleep
が例外を発生させた時,Thread.sleep
の呼び出し元であるroundTrip
に対応が任されます.これが例外が投げられた,ということです.- そこで,
roundTrip
の呼び出し元であるrun
に対応を任せましょう. - さらに,
run
でも,呼び出し元であるmain
に処理を任せる事にします. - 同じく,
main
も呼び出し元に対応を任せましょう. - すると,実行環境が対応されなかった例外をスタックトレースという形で出力し,プログラムが終了するようになります.
throws 節
呼び出し元に対応を任せるには,メソッドのシグネチャに throws
節を追加します.
すなわち,メソッドの宣言部分を以下のように変更してください.
public class RoundTrip{
void run() throws InterruptedException{
// ... 省略
// roundTripで IntrruptedException が発生する可能性がある.
this.roundTrip(circle);
}
void roundTrip(EZCircle circle) throws InterruptedException{
// .... 省略
// Thead.sleepの呼び出しで IntrruptedExceptionが発生する可能性がある.
Thread.sleep(100);
}
public static void main(String[] args) throws InterruptedException{
RoundTrip trip = new RoundTrip();
// runでIntrruptedExceptionが発生する可能性がある.
trip.run();
}
}
このようにプログラムを変更し,コンパイルしてみましょう.今度はコンパイルできたはずです. 次のような実行結果となるはずです.
このように,アニメーションを行うには,スリープが必要です. 一方,スリープを行うには,例外への対応が必要になります. 今後,ファイルの入出力を扱う時にも例外機構は必要になってきますので, どのような機構であるのか,しっかりと押さえておいてください.
スリープを行わないと,環境によっては目にも留まらぬ速さでアニメーションが繰り広げられます.
例題4. 鉛直投げ上げ運動のアニメーション
以下の実行結果になるよう,鉛直投げ上げ運動のアニメーションを作成してみましょう.
(アニメーションが途中で終わっているので変な挙動のように見えますが,バウンドし続ける動きになっています)
クラス名は Bound
としてください.
$y$方向の0が一番上,下方向がプラスになっていますので,通常の投げ上げとは方向が逆になっています(上方向に重力がかかっていると思ってください). 必要な式は次の通りです.
- 時間$t$は0から始まり,0.1ずつ増加するものとする.
- 投げ上げ運動,自由落下運動それぞれに切り替わるとき,$t=0$となる.
- 重力加速度 $g=9.8$.
- 投げ上げ運動のとき(下向きに動くとき)
- 初速 $v_0 = 85.0$.
- 初期位置 $y_0 = 0.0$.
- $v<0$となったとき,自由落下運動に切り替わる.
- 現在位置を求める.
- $y = v_0 t - \frac{1}{2}gt^2$
- その時の速度を求める.
- $v = v_0 - gt$
- 自由落下運動のとき(上向きに動くとき)
- 初速 $v_0 = 0.0$
- 初期位置 $y_0 = 368.62$
- $y_0$ は投げ上げ運動の時の $v=0$となるときであるから,そのときの$t$は次の式で求められる.
- $0 = v_0 - gt, v_0 = gt, t = \frac{v_0}{g}$,
- この時の$t$を鉛直投げ上げの位置を求める式に代入し,
- $y = v_0 \frac{v_0}{g} - \frac{1}{2}g (\frac{v_0}{g})^2 = \frac{2v_0^2 - v_0^2}{2g} = \frac{v_0^2}{2g} = \frac{85.0^2}{2 * 9.8} = 368.62$
- $y_0$ は投げ上げ運動の時の $v=0$となるときであるから,そのときの$t$は次の式で求められる.
- $y < 5$となったとき,投げ上げ運動に切り替わる.
- 本来の物理世界であれば,$v_{i -1}$に跳ね返り係数をかけ,$v_i$とするが,今回は跳ね返り係数を1としている.
- 現在位置を求める.
- $y = y_0 - (v_0 t - \frac{1}{2}gt^2)$.
- その時の速度を求める.
- $v = v_0 - gt$(投げ上げ運動と同じ)
練習問題
1. 図形の描画
次の図になるようにプログラムを作成してみましょう.
色は必ずしもこの通りでなくて構いません.
プログラム名は,DrawShapes2
としてください.
2. コッホ曲線(Koch curve)の描画
コッホ曲線は,線分を三等分し,分割点を頂点とした正三角形を描く線です. この作図を無限に繰り返すことで,線分の長さが$\infty$になります. コマンドライン引数でコッホ曲線の $n$ を指定できるようにしましょう.
クラス名は,KochCurve
としてください.
コッホ曲線の例を以下に示します.
上記のように,$(x_1, y_1)$と$(x_5, y_5)$が指定された時,$(x_2, y_2)$〜$(x_4, y_4)$を求めましょう.
- $l=(\sqrt{(x_5 - x_1)^2 + (y_5 - y_1)^2})/3$
- $(x_2, y_2) = (x_1 + l, y_1)$
- $(x_3, y_3) = (x_2 + l\cos{\frac{\pi}{3}}, y_2 + l\sin{\frac{\pi}{3}})$
- $(x_4, y_4) = (x_1 + 2l, y_1)$
$n=1$までは上記のように計算できますが,$n>2$の場合はこの計算では求められません. 次の例を元に考えてみましょう.
上記のように,$(x_1, y_1)$と$(x_5, y_5)$が指定された時,$(x_2, y_2)$〜$(x_4, y_4)$を求めましょう. 元々の傾きを$\theta$として示します.
- $l=(\sqrt{(x_5 - x_1)^2 + (y_5 - y_1)^2})/3$
- $(x_2, y_2) = (x_1 + l\cos{\theta}, y_1 + l\sin{\theta})$
- $(x_3, y_3) = (x_2 + l\cos{(\theta + \frac{\pi}{3})}, y_2 + l\sin{(\theta + \frac{\pi}{3})})$
- $(x_4, y_4) = (x_3 + l\cos{(\theta + \frac{\pi}{3} - \frac{2\pi}{3})}, y_3 + l\sin{(\theta + \frac{\pi}{3} - \frac{2\pi}{3})}) = (x_3 + l\cos{(\theta - \frac{\pi}{3})}, y_3 + l\sin{(\theta - \frac{\pi}{3})})$
この計算式で,コッホ曲線を描いてみましょう. 再帰呼び出しを利用すると良いでしょう. $(x_2, y_2),(x_3, y_3)$間の直線を引く時,また,$(x_3, y_3),(x_4, y_4)$間の直線を引く時に それぞれを$(x_1, y_1),(x_5, y_5)$として再帰呼び出しを行えばコッホ曲線を描けるでしょう.
ヒント
- 次のようなメソッドを用意しましょう.このメソッドを呼び出すことで,2点の間にコッホ曲線を描けるようになります.
-
void drawKoch(Integer x1, Integer y1, Integer x5, Integer y5, Integer dimension, Double angle){ if(dimension == 0){ // (x1, y1)から(x5, y5)まで線を引く. } else{ Double length = // (x1, y1), (x5, y5) 間の長さの 1/3.これが l となる. Double delta = Math.PI / 3.0; // (x2, y2) を求める. // (x1, y1)から(x2, y2)まで線を引く. // (x3, y3) を求める(θ は angle + delta). this.drawKoch(x2.intValue(), y2.intValue(), x3.intValue(), y3.intValue(), dimension - 1, angle + delta); // (x4, y4) を求める(θ は angle - delta). this.drawKoch(x3.intValue(), y3.intValue(), x4.intValue(), y4.intValue(), dimension - 1, angle - delta); // (x4, y4)から(x5, y5)まで線を引く. } }
-
- Javaで,sin, cosを計算するには,
Math.sin
,Math.cos
メソッドを利用しましょう. 引数にはラジアンの値を渡してください.$\pi$を利用するには,Math.PI
という変数を利用してください. すなわち,$\sin \frac{\pi}{3}, \cos \frac{\pi}{3}$をJavaで求めるには,次のようなコードを用いてください.-
Double sinValue = Math.sin(Math.PI / 3.0); Double cosValue = Math.cos(Math.PI / 3.0);
-
Double
型をInteger
型として扱うには,Double
型の変数に対して,intValue
メソッドを呼び出しましょう.
実行例
3. コッホ曲線(Koch curve)のアニメーション描画
コッホ曲線を$n=0$から$n=5$までを3秒程度で更新して描いてみましょう.
クラス名は,KochCurveAnimation
としてください.
今まで描画した内容を消したい場合は,EZ.removeAllEZElements()
メソッドを呼び出してください.
4. 斜方投射
例題3と例題4を合わせた動きをするボールを描きましょう.
$x$軸方向には,例題3を,$y$軸方向には,例題4の動きを設定すれば良いでしょう.
クラス名を ThrowingExercise
にしてください.
実行例
上記の画像をクリックするとアニメーションが開始され,もう一度クリックすると元の画像に戻ります.
5. アニメーション
EZ Graphics を利用して,自由にアニメーションを描いてください.
クラス名は Animation
としてください.
内容は自由です.
まとめ
- Javaのグラフィックス描画の方法の一つに,EZ Graphicsがある.
- 具体的な描画方法は,APIドキュメントを参照のこと.
- 例外機構
- 実行時エラーの通知機構として近年のプログラミング言語に採用されている.
- 例外は,検査例外と非検査例外の2種類が存在する.
- 検査例外は例外が発生した時の処理をプログラム中に明示しなければならない.
- 代表的な検査例外
IOException
: 入出力エラーが起こった時.InterruptedException
: スリープ中に割り込みが発生した時.
- 代表的な検査例外
- 非検査例外は例外発生時の処理を書かなくても良い.
- 十分にデバッグされたプログラムでは発生しない例外であるため.
- 対応処理を書いていない場合,例外が発生した時プログラムが落ちる.
- 代表的な非検査例外
NullPointerException
: 参照先の実体が存在しない変数に対して操作を行なった時.IndexOutOfBoundsException
: 有効な範囲を超えてリストにアクセスしようとした時.NumberFormatException
: 文字列を数字に変換する処理で,変換に失敗した時.
- 検査例外は例外が発生した時の処理をプログラム中に明示しなければならない.
- 例外が発生した時,その場で対応するか,呼び出し元に対応を任せるかの2通りの対処法がある.
- 例外が発生した時の責任をメソッド呼び出し元に任せるには,メソッドのシグネチャに
throws
節を追加する.返り値の型 メソッド名(メソッドの引数) throws 例外名{ メソッドのボディ }
- 例外が発生した時,その場で対応するには,
try-catch
構文を利用する.- この授業では扱わない.
- 使う場合には,自分で調べること.
- 例外が発生した時の責任をメソッド呼び出し元に任せるには,メソッドのシグネチャに