最終更新日



タートルグラフィックスとフラクタル(アプレット1)

1 タートルグラフィックス 2 問題点・苦労した点  3 フラクタル図形
 4 J2SE1.2.2でコンバイルしたフラクタル図形

1 タートルグラフィックス

最初のアプレット・プログラムとして、ロゴのタートルもどきをJavaアプレットでトライしてみた。相対座標で描画できるのが特徴で、再帰も使うと、フラクタル図形などが簡単に描けるよさがある。1章の例は渦巻き模様(下記)。かなり苦労したか、それは2で述べることにして、まずはリスト全文から。そして、3ではフラクタル図形の世界で遊びましょう。また、マンデルブロート、カオス も参照ください。

[ブラウザソフトのJava対応]
 アプレットを見るには、Java対応、すなわち、JavaのVMをそなえたブラウザソフトでないといけない。マイクロソフトのIEは、Java1.1という古いシステムをサポートしていたが、XPのSP1以降、サポートをうちきりました。その後は、各メーカが独自に対応しているが、統一はされていない。もし、このページのアプレット画面が見られない場合は、ブラウザソフトのJava対応 を参照ください。

なお、Java1.1という古いシステムをサポートしていた頃の遺物、J2SE1.2.2でコンパイルし直したものを4章に残しておきます。


    渦巻き      91度
    渦巻き181  181度
    渦巻き145  145度

1 初期設定とメソッド呼び出し例(渦巻きの例)

//パス移動、クラスパス、コンバイル情報をコメントにして保存
//cd c:\HPTak\Program\Java\Fractal
//set CLASSPATH=c:\HPTak\Program\Java\Fractal
//javac Uzu\*.java

package Uzu;
import java.applet.Applet;
import java.awt.*;
import Turtle.*; //Turtleパッケージをとりこむ

public class Uzu extends Applet {
 //パッケージのTurtleクラスのインスタンス化
  Turtle t=new Turtle();
 //html文のパラメータの読み込み準備
  String ks=null;
  
  public void paint(Graphics g) { 
    //初期設定
       //setBackground(Color.black); 適当でない
        Dimension d = getSize();  //画面サイズ取得
       //描画原点を画面中央に
      	t.xyk[0]=d.width/2;
   	t.xyk[1]=d.height/2;
   	t.xyk[2]=0; //角度は0
        t.home(g);  //背景を黒に
       //のhtml文のパラメータを読み込み、渦巻きの角度kを決める
        ks=getParameter("kakudo");
	if (ks==null) ks="91"; //パラメータに記述がない場合の処置
        int k=Integer.parseInt(ks);
       //タイトル文字を赤で
        g.setColor(Color.red);
        t.text(g,"二進木(10," + ks + ")",60);
       //図形を緑色で描画
        g.setColor(Color.green);
       //渦巻きを呼び出す
        uzumaki (g,10,k);
   }

  void uzumaki(Graphics g,int d,int k) {
 	if (d>700) return;
 	t.fd(g,d);
	t.lt(g,k);
	uzumaki(g,d+2,k);
     }
}

2 タートルの準備

//タートルグラフィックス
//cd c:\HPTak\Program\Java\Fractal
//javac Turtle\*.java

package Turtle; //パッケージ化

import java.applet.Applet;
import java.awt.*;

public class Turtle extends Applet {
   public int x;
   public int y;
   public int k;
   public int xyk[]={x,y,k};

  // x,y座標と角度kの保持
   public void current (int xyk[]) {
      	this.x=xyk[0];
   	this.y=xyk[1];
   	this.k=xyk[2];
   }

  //前進
   public void fd(Graphics g, double d) {
	current(xyk);
      	int curr_x=xyk[0];
   	int curr_y=xyk[1];
   	k=xyk[2];
	double kk = Math.PI*(k*(double)1.0)/180.0;
	int x = curr_x + (int)(d * Math.cos(kk));
	int y = curr_y + (int)(d * Math.sin(kk));
        //タートルを遅くして
	wait1(500);
        //描画
	g.drawLine(curr_x,curr_y,x,y);
       //変更されたxy座標をcurrent(xyk)へ
	xyk[0]=x;
   	xyk[1]=y;
  }

  //後退
  public void bk(Graphics g, double d) {
 	fd (g,-d);
 }

//右へ
  public void rt(Graphics g, int k) {
	current(xyk);
   //変更された角度kをcurrent(xyk)へ
      	xyk[2] += k;
  }

//左へ
   public void lt(Graphics g, int k) {
	current(xyk);
      	xyk[2] -= k;
  }

//タートルの動きを遅くする
  public void wait1(int t) {
     int k=0;
     int tt=t*10000; 
     for (int i=0; i<10000000; i++) {
	 if (i==tt) break;
     	  k+=1;
     }
  }

  public void setc(Graphics g, int c) {
	//白、オレンジ、赤、ピンク、グリーン、青、黄、黒、マゼンダ、シアン
	Color colors[] = {Color.white,Color.orange,Color.red,Color.pink,
	      Color.green,Color.blue,Color.yellow,Color.black,Color.magenta,
	      Color.cyan,Color.lightGray,Color.darkGray,Color.gray};
        g.setColor(colors[c]);
  }

 //多角形の塗りつぶし
  public void fill(Graphics g,int polyx[],int polyy[]) {
	g.fillPolygon(polyx,polyy,polyx.length);
  }

//背景色と描画の色初期設定
  public void home(Graphics g) {
        current(xyk);
   	int polyx[]={0,2*xyk[0],2*xyk[0],0};
   	int polyy[]={0,0,2*xyk[1],2*xyk[1]};
   	g.setColor(Color.black);
   	fill(g,polyx,polyy);
   	g.setColor(Color.green);
   }

 // 文字列の描画
  public void text(Graphics g,String s,int baseline) {
    g.setFont(new Font("Serif", Font.BOLD, 36));
    g.drawString(s, 5, baseline);
  }
}

2 渦巻き模様-

1-1は渦巻きの例で始めたが、角度 kを 360*2/5=144度 とすると星型になるが、145度と少しずらすと渦巻きらしくなる。 90度とか180度、60度でも、1度か2度ずらすと面白い図形になる。 実際のトライ例は、冒頭にも出したが、渦巻き などにある。

入力パラメータ、角度を変更して図形を変えられるが、説明は2-3にある。なお、渦巻きのように、プログラムの最後にある再帰は単純な繰り返しで簡単であるが、再帰が途中に入ると複雑になる。3章のフラクタルで例を示す。

2 問題点・苦労した点

1 タートルグラフィックスの特徴、現在位置と向きをどう扱うか?

タートルグラフィックスの特徴は、現在の位置、角度からの相対表記で描画する点にあり、右へと左へ rt(right),lt(light)で向きを変え、前進と後退 fd(forward),bk(backward)で、ある長さを移動する。Javaでは、x,y座標の絶対位置で描画するので、sin,cosを使って変換する。

最も問題だったのは、現在の位置と角度を常に保持することだった。グローバル変数というのはなさそうだし、publicをつけてみたりすると、たとえば「クラス Tree は public であり、ファイル Tree.java で宣言しなければなりません。」などというメッセージをもらったりする。いろいろとトライしたが、皆失敗して、結局、current(xyk)というメソッドで配列にして渡すことにした。配列なら、操作する度に変化する値を受け渡しできるから。もっといい方法はありそうだが。

2 パッケージ化、ようやく成功

1-2は、一度コンパイルしておいて、必要なときに呼び出して使いたい。Turtleというソースファイルにして、パッケージ化するのがよかろうと、いろいろ試みて、最初は失敗続きであった。アプレットにはパッケージ化はだめということもあるのかと思ったくらいである。

下記は、1-1を(3-2のHilbert)、1-2 Turtleとは別のファイルにした場合のエラーメッセージ。パッケージとしたTurtleはコンパイルできているが、使う段階で、パッケージ Turtle は存在しないとして、1-1はコンパイルさえできない。

cd c:\HPTak\Program\Java\Fractal
javac Hilbert\*.java
Hilbert\*.java:6: パッケージ Turtle は存在しません。
import Turtle.*;
^

「やさしいJavaプログラミング」(萩本順三著 AI出版 1998.11)の20章に、アプレットの場合について、やや詳しく出ていたことで解決した。上記エラーは、またしても、CLASSPATHの問題であった。
c:\HPTak\Program\Java\Fractalに、1-2をTurtle\Turtle.java として、パッケージ化したが、その呼び出しにあたって、
set CLASSPATH=c:\HPTak\Program\Java\Fractal が必要というわけである(アプレットの場合は、普通にコンパイルする場合は不要だったので盲点になっていた)。

フォルダの構成は下記。UZU、Tree、Hilbertなどの呼び出しのhtml文は、ひとつ上のフォルダにおき、code="Hilbert.Hilbert.class"のように記述する。

<html>
<head>
<title>渦巻き181</title>
</head>
<body>
  <applet code="Uzu.Uzu.class" width=1024 height=768>
  <param name=kakudo value="181">
  </applet>
</body>
</html>

3 入力パラメータ

渦巻きでは角度、二進木やヒルベルトでは繰り返し回数をパラメータにして、入力で変化させたい。アプレットでない場合は、コマンドライン入力だが、アプレットではどうするのかと悩んでいた。上記の本でそれも解決。呼び出しのhtml文の中に、上の例にあるように、
<param name=kakudo value="181"> といれて、Uzu.java 1-1 にあるように、s=getParameter("kakudo"); として読みこむことで解決。

 //html文のパラメータの読み込み準備
  String ks=null;
  
  public void paint(Graphics g) { 
      ..........
     //のhtml文のパラメータを読み込み、渦巻きの角度kを決める
        ks=getParameter("kakudo");
	if (ks==null) ks="91";
        int k=Integer.parseInt(ks);
      ..........

上記の中のif (ks==null) 文は、パラメータの記述がない場合の対策である。逆に、3-1や3-2の二進木やヒルベルトの再帰の解説では、パラメータを2つ欲しくなった。どうするのかと思っていたら、 <param name=.... value="..."> を必要なだけ並べればよいのである。

実際に変更するには、Uzu.htmlを実行して、「表示-ソース」で変更、上書き保存して、Uzu.html画面にもどって更新とすればよいが、やはり、インターネット上での変更は無理、ハードディスク上のコードに限られるようだ。そこで、あらかじめ、バラメータを変更してあるhtml文をいくつか用意した。1章冒頭の渦巻きの3つの例がそれである。

4 オブジェクトの概念と操作

Math.sin,Math.cosの処理結果、x,yはdouble型であるが、アプレットのdrawLineの引数はint型ある。doubleからintにするのに苦労した。オブジェクトの概念と操作がほんとうには分かっていないことをさらけ出した。具体的にいうと、void fd(Graphics g, double d)の中で

   void fd(Graphics g, double d) {
	.........
	//このようにしないと、DoubleクラスのintValue()でintにできない。
	Double dy=new Double(d * Math.sin(kk));
	.........
	int x = curr_x + dx.intValue();
	.........
	g.drawLine(curr_x,curr_y,x,y);
	.........
  }

new演算子を使って、Double型のオブジェクト(インスタンス)として、Doubleクラス のintValue()メソッドで整数型にするというものだが、これがわからなくて苦労した次第。d * Math.sin(kk)は、double型変数であっても、Double型のオブジェクトではないということ? だが、結局はもっと簡単な方法があるのではないかということで、1-1のリストのように、(double)とか(int)を変数ないし、計算式の前につけることにした。

ただ、その方法もいきすぎて失敗したのが、アブレット2 の4章 シダの葉である。
dot(g,curr_x+(int)(600*x2)-300,curr_y-(int)(600*y2)); とすべきところを
dot(g,curr_x+600*(int)x2-300,curr_y-600*(int)y2); としたら、画面にほとんど何も出てこなかった。原因がわからず、苦労させられたひとつである。

5 待てという命令とThread

4章のマンデルブロートは計算量が多くて描画過程が見えるが、3章のフラクタル図形の二進木やヒルベルト曲線は描画が速すぎて、描き終わった図形しか見えないようだった。それだけ、最近のJavaもパソコンも速くなったということであろう。そこで、wait1(int t)というメソッドを前進 fdの中にいれることにした。wait1の実態は、無意味な繰り返しを最大1000万回くりかえすだけである。その回数は引数のtで調整する。tは200とか500とか3桁だが、wait1の中で1万倍している。ミリセコンドとはいかないが、近い感じにはなっているか?

アニメとか動きのあるシミュレーションには必須という、threadの勉強をかねて、thread.sleep(500)というようなしゃれた方法でやりたかったが、タートルを経由して描画するという、このような複雑な場合には、いまのところ、うまくいっていない。 下記を、たとえばhilbertを呼び出しているクラスにいれてみたが、できあがりを時間をおいて再描画する以上のことにはならないようだ。fdを含むTurtleの側にいれると、コンパイルエラーになる。ということで、理解が進むまで保留することにして、上記の簡単な逃げ手を採用。

public class Hilbertthread extends Applet {
implements Runnable{
  Turtle t;
  Thread th;        
  
  public void init() {
	th=new Thread(this);
      	th.start();
  }

  public void run(Graphics g) {
    try {
	while(true) {
	   repaint();
	   Thread.sleep(1000);
	}
    }
    catch (Exception e) {
    }	
  }

  public void udate(Graphics g) {
	paint(g);
  }

6 画面サイズ取得のメソッドの位置

タートルの最初の位置と角度を初期化し、背景画面を黒くする役割をhomeメソッドにもたせようとしたが、画面サイズ取得のメソッド、Dimension d = getSize()や、背景色を設定する、setBackground(Color.black)をおく位置はきまっているようで失敗した。

これらは、メイン相当のpublic void paint(Graphics g) の中に書く必要があるようで、リストのようにした。しかし、setBackground(Color.black)を使うと、最初は灰色の背景に描画し、それが終わってから背色を黒にしてから再度描画するようだ。なぜ? しかし、これでは具合が悪いので、いったん、使わないことにしたhomeメソッドを復活させ、画面全体を黒くぬりつぶす方法で背景を黒くすることにした。また、描画の色の初期値として緑色を選んだ。

ペンの色指定のsetcも作ったが、マンデルのようなときには必要である。普通は、g.setColor(Color.green)でよいが、4章のマンデルブロートでは必要になり、そこに出す。また、点を打つのもやはり4章にある。

3 フラクタル図形

    2進木      
    ヒルベルト曲線  

1 2進木

public class Tree extends Turtle {
 //パッケージのTurtleクラスのインスタンス化
  Turtle t;
 //html文のパラメータの読み込み準備
  String ns=null;
  String ds=null; 

  public void paint(Graphics g) { 
        t=new Turtle();
        .......
       //のhtml文のパラメータを読み込み、長さddと繰り返し回数nを決める
        ds=getParameter("nagasa");
        if (ds==null) ds="50";
        int dd=Integer.parseInt(ds);
        ns=getParameter("kaisu");
        if (ns==null) ns="6";
        int n=Integer.parseInt(ns);
        g.setColor(Color.red);
        t.text(g,"2進木(" + dd + ",20," + n + ")",50);
        //150だけ下げてから、長さ70の幹を描く
        t.lt(g,90);
        g.setColor(Color.black);
        t.bk(g,150);
        g.setColor(Color.green);
        t.fd(g,70);
        tree(g,dd,20,n);
   }

   void tree(Graphics g,double d,int k,int n) {
 	if (n==0) return;
	t.lt(g,k);
 	t.fd(g,2*d);
 	tree(g,0.9*d,k,n-1);
 	t.bk(g,2*d);
 	t.rt(g,2*k);
 	t.fd(g,d);
	tree(g,0.9*d,k,n-1);
 	t.bk(g,d);
 	t.lt(g,k);
     }
}

nが0のときは、再帰のtreeはすべて無効になり、fd,bk,rt.lt をたどると、角度kの2倍開いた2本の枝になる。nが1のときは、tree(g,0.9*d,k,n-1)の再帰が2つあるので、その枝の左右の先端に、長さが0.9倍と短くなった、nが0のときの枝ができる。以下、同様にして大きくなっていく。長さddと繰り返し回数nを変えた例を下記に示す。Treeを描いているhtmlのソースを開き、自由にパラメータを変えられるといいのだが、ネット上ではそれは無理らしいので、苦肉の策。

     2進木1   n=1 dd=100
     2進木2   n=2 dd=100
     2進木     n=6 dd=50(3章冒頭に同じ)
[参考] Logoの例
TO TREE :長さ :角度 :深さ
 IF :深さ = 0  [STOP]
 LT :角度
 FD 2 * :長さ
 TREE .9 * :長さ :角度 :深さ - 1
 BK 2 * :長さ
 RT 2 * :角度
 FD :長さ
 TREE .9 * :長さ :角度 :深さ - 1
 BK :長さ
 LT :角度
END 

2 ヒルベルト曲線

public class Hilbert extends Applet {
  ..........
  public void paint(Graphics g) {
        ..........
        //のhtml文のパラメータを読み込み、長さddと繰り返し回数nを決める
        ds=getParameter("nagasa");
        if (ds==null) ds="10";
        int dd=Integer.parseInt(ds);
        ns=getParameter("kaisu");
        if (ns==null) ns="6";
        int n=Integer.parseInt(ns);
        g.setColor(Color.red);
        t.text(g,"ヒルベルト曲線(" + dd + ",90," + n + ")",60);
       //描画開始位置を左上にする
        t.xyk[0]=200;
   	t.xyk[1]=100;
        g.setColor(Color.green);
        hilb(g,dd,90,n);
   }
  
   void hilb(Graphics g,double d,int k,int n) {
 	if (n==0) return;
	t.rt(g,k);
 	hilb(g,d,-k,n-1);
 	t.fd(g,d);
 	t.lt(g,k);
 	hilb(g,d,k,n-1);
 	t.fd(g,d);
 	hilb(g,d,k,n-1);
 	t.lt(g,k);
 	t.fd(g,d);
 	hilb(g,d,-k,n-1);
 	t.rt(g,k);
     }

実際のトライ例は、下記だが、繰り返し回数が多い場合は、やや遅いかもしれない。1-2の前進 fdの中にいれた、wait1(500)ではなく、wait1(100)あたりがよいのだが、パッケージの変更は面倒なので、そのままにした。

nが0のときは、再帰のhilbはすべて無効になり、fd,rt.lt をたどると、コの字型になる。nが1のときは、hilb(g,d,k,n-1)やhilb(g,d,k,n-1)が4回あるので、そのコの字型が向きを変えながら、4ケできて、大きなコの字型の3本の線で結ばれる。nが2のときは、n=1でできた4ケが結ばれるという形で増えていく。

     ヒルベルト曲線1  n=1 dd=100
     ヒルベルト曲線2  n=2 dd=100
      ヒルベルト曲線   n=6 dd=10(3章冒頭に同じ)
[参考] Logoの例
TO HILB :長さ :角度 :深さ
  IF :深さ = 0 [ STOP ]
  SETC 1 + RANDOM 7 
  RT :角度
  HILB :長さ (-1 * :角度) ( :深さ - 1 ) 
  FD :長さ
  LT :角度 
  HILB :長さ :角度  ( :深さ - 1 )
  FD :長さ
  HILB :長さ :角度 ( :深さ - 1 ) 
  LT :角度 
  FD :長さ
  HILB :長さ (-1 * :角度) ( :深さ - 1 ) 
  RT :角度 
END

4 J2SE1.2.2でコンバイルしたフラクタル図形

かっては、IEもNetscapeもJava1.1という古いシステムをサポートしていたので、わざわざ、2003/10時点で入手し得る古いJ2SE1.2.2でコンパイルし直したものを別途、アップしていたが、ブラウザのJavaに対する対応は大きく変わってきた(詳しくはブラウザソフトのJava対応 参照)。別途アップのページは廃止し、ここに移すことにした(2005/1)。

    渦巻き      91度
    渦巻き181  181度
    渦巻き145  145度
    2進木 
    2進木1   n=1 dd=100
    2進木2   n=2 dd=100
    ヒルベルト曲線1   n=1 dd=100
    ヒルベルト曲線2  n=2 dd=100
    ヒルベルト曲線  
ページ独自カウント