[Top ページ] [他のサンプル]

Wave.java


import java.applet.Applet;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.awt.image.MemoryImageSource;
import java.awt.image.PixelGrabber;


/**
 * 「波」
 * 水面の波をシミュレートするものです。
 */

public final class Wave extends Applet implements Runnable, MouseListener, MouseMotionListener {
	// フィールドの幅と高さ
	// ここを変更してコンパイルし直せば、大きさを変えることが出来ます。
	private final int cWidth = 256, cHeight = 256;
	Thread aThread_ = null;
	private Graphics gra_;
	RGBMemoryImage memImage_, memBgImage_;
	private volatile int xcounter = 360;
	private final int counterMax = 800;

	// 壁の情報
	// byte型にした方がメモり消費が少なくなってキャッシュに
	// ヒットしやすくなるかな~ なんて思ったんですが....
	//byte[] wall;

	// 実行時最初に一度だけ呼び出される。
	public void init() {
		// 画面の配置など....
		setBackground(Color.lightGray);

		gra_ = getGraphics();
		// マウス関連のイベント処理オブジェクトを指定
		addMouseListener(this);
		addMouseMotionListener(this);
		// メモリイメージの初期化
		memImage_ = new RGBMemoryImage(cWidth, cHeight);
		memBgImage_ = new RGBMemoryImage(loadImage("bg.jpg"));
	}

	// ブラウザ上で他のページから戻ってきたときなどに呼び出される。
	// 一番最初も必ず呼び出される。
	public void start() {
		// スレッドの開始
		if (aThread_==null)
			(aThread_ = new Thread(this)).start();
	}
	// ブラウザ上で他のページに移動したときなどに呼び出される。
	public void stop() {
		// スレッドの停止
		if (aThread_ != null) { aThread_.interrupt(); }
		aThread_ = null;
	}
	// イメージを読み込む
	private Image loadImage(String fname) {
		return getToolkit().getImage(getClass().getResource(fname));
	}
	/**
	 * 波の表示のメイン部分
	 */
	public void run() {
		int i;
		byte[] wall = new byte[cWidth * cHeight];
		int[] field1 = new int[cWidth * cHeight]; // 2ステップ前の水面の高さ
		int[] field2 = new int[cWidth * cHeight]; // 1ステップ前の水面の高さ
		int[] field3 = new int[cWidth * cHeight]; // 現在の水面の高さ
		int[] pixels = memImage_.getPixels();
		int[] bgPixels = memBgImage_.getPixels();
		int bgWidth = memBgImage_.getWidth();

		// 初期化
		int x,y;
		i = 0;
		for (y=0;y < cHeight;y++) {
			for (x = 0; x < cWidth;x++) {
				pixels[i] = 0xff000000; // FullColorDirectImage.RGB(0,0,0);
				field1[i] = field2[i] = 0;
				// 壁
				if (x == 0 || y == 0 || x == cWidth - 1 || y == cHeight - 1) {
					wall[i] = 1; // 画面の端の壁
				} else {
					wall[i] = 0;
				}
				i++;
			}
		}

		int counter = 0;
		int bx = 0, by = 0;
		// メインループ
		while(true) {
			boolean isView = (counter % 3) == 0;
			// 波の計算
			i = cWidth + 1;
			for (y = 1; y < cHeight - 1; y++) {
				for (x = 1; x < cWidth - 1; x++) {
					if (wall[i] != 0) {
						// 壁
						int c = bgPixels[i+cWidth+1];
						c -= (c & 0xfefefe) >> 1;
						pixels[i] = 0x707070 + c;
						field3[i] = 0;
					} else {
						// 実際の波の計算はここ(ポイントはこの一行だけなんですよね~)
						field3[i] = ((((field2[i - 1] + field2[i + 1] + field2[i - cWidth] + field2[i + cWidth]) / 2) - field1[i]) * 63) / 64;
						if (isView) {
							// 画面に出力する色の濃さを計算
							// 屈折の計算(ぜんぜん厳密ではない)
							// X方向のずれ
							int dx = ((field2[i+1]-field2[i]) / 4096);
							if (dx > 12) dx = 12; else if (dx < -12) dx = -12;
							// Y方向のずれ
							int dy = ((field2[i+cWidth]-field2[i]) / 4096);
							if (dy > 12) dy = 12; else if (dy < -12) dy = -12;
							// 範囲外にならないように調整
							if (x + dx < 0) dx = -x; else if (x + dx > cWidth-1) dx = cWidth - x - 1;
							if (y + dy < 0) dy = -y; else if (y + dy > cHeight-1) dy = cHeight - y - 1;

							int ad = x + dx + (y + dy + by) * bgWidth + bx;
							int c = bgPixels[ad];
							// 左上方向へ下がっているところを少し明るくする処理
							// (左上から光がさしこんでいるのを表現)
							int a = (field2[i+1+cWidth]-field2[i]) / 64;
							if (a > 160) a = 160;
							if (a > 0) {
								int c0 = c & 0xff00;
								c = (c & 0xffff00ff) | ((c0 + (0xff00 - c0) * a / 512) & 0xff00);

								c0 = c & 0xff;
								c = (c & 0xffffff00) | ((c0 + (0xff - c0) * a / 512) & 0xff);

								c0 = c & 0xff0000;
								c = (c & 0xff00ffff) | ((c0 + (0xff0000 - c0) / 512 * a) & 0xff0000);
							}
							pixels[i] = c;
						}
					}
					i++;
				}
				i += 2;
			}
			// 画面に表示
			if ( isView ) {
				memImage_.flush(); // pixelsの変更をイメージに反映
				gra_.drawImage(memImage_.getImage(), 0, 0, null); // 画面に表示
				// ほかのスレッドに処理を渡す。
				try {
					Thread.sleep(20); // 一定時間待つ。ここの値を大きくすると遅くなります。
				} catch(InterruptedException e) { return; }
			}
			counter++;
			// field3 -> field2 -> field1 -> field3 の入れ替え.
			// くるくる回しているといった感じ
			int[] field0 = field1; // バッファー
			field1 = field2;
			field2 = field3;
			field3 = field0;

			// マウスでクリックされたりドラッグされたとき
			if (mouseFlag_) {
				raiseWave(field2,mx_,my_, 1024);
				mouseFlag_ = false;
			} else if (xcounter > counterMax) {
				xcounter -= 60;
				mx_ = (int)(Math.random() * (cWidth - 16) + 8);
				my_ = (int)(Math.random() * (cHeight - 16) + 8);
				raiseWave(field1,mx_,my_, 1024);
			}

			field2[cWidth / 2 + (cHeight / 8) * cWidth] += 512;

			xcounter++;
		}
	}

	// 水面を盛り上げる
	final int startSize = 6;
	void raiseWave(int[] field,int cx,int cy, int startHeight) {
		int x, y;
		if (cx < startSize || cx > cWidth-1-startSize || cy < startSize || cy > cHeight - 1 - startSize) { return; }
		for (x =- startSize;x < startSize;x++) {
			for (y = -startSize; y < startSize; y++) {
				double r = x * x + y * y;
				int ad = (x + cx) + (y + cy) * cWidth;
				if (r < startSize * startSize) {
					field[ad] += (int)( (Math.cos(Math.sqrt(r) / startSize * 3.14159) + 1) * 256 * startHeight );
					if (field[ad] > 1024 * 256 * 16) { field[ad] = 1024 * 256 * 16; }
				}
			}
		}
	}

	// mx,my,mouseFlagはマウスが押されたこととその位置を
	// 波を動かしているスレッドに通知するためにある。
	// マウスが押されたときのマウスカーソルの位置
	// volatileっていうのは、2つのスレッドで共有するような変数に
	// 付けておくおまじない。今のところ、このおまじないがないと動かない
	// JVMは見たことないですが、まぁ 念のためかな...
	private volatile int mx_, my_;
	// マウスが押された?
	private volatile boolean mouseFlag_ = false;
	// Mouse event
	// マウスボタンが押されたとき...
	public void mousePressed(MouseEvent ev) {
		mx_ = ev.getX();
		my_ = ev.getY();
		mouseFlag_ = true;
		xcounter = 0;
	}
	public void mouseReleased(MouseEvent ev) {}
	public void mouseClicked(MouseEvent ev) {}
	public void mouseEntered(MouseEvent ev) {}
	public void mouseExited(MouseEvent ev) {}

	// マウスがドラッグされたとき
	public void mouseDragged(MouseEvent ev) {
		int x = ev.getX();
		int y = ev.getY();
		if (x < 1 || x > cWidth - 2 || y < 1 || y > cHeight - 2) return;

		// 波を起こすモードの時
		mx_ = x;
		my_ = y;
		mouseFlag_ = true;
	}
	public void mouseMoved(MouseEvent ev) {
		xcounter = 0;
	}


	/**
	 * MemoryImageSource をラップしたクラス。
	 */
	final class RGBMemoryImage {
		private int pixels_[];
		private Image image_ = null;
		private int width_ = -1, height_ = -1;
		private MemoryImageSource memoryImageSource_;

		private void createImage() {
			memoryImageSource_ = new MemoryImageSource(width_, height_, pixels_, 0, width_);
			memoryImageSource_.setAnimated(true);
			image_ = Wave.this.createImage(memoryImageSource_);
		}

		private void createPixels(Image image) {
			int pixels[] = new int[width_ * height_];
			PixelGrabber pg = new PixelGrabber(image, 0, 0, width_, height_, pixels, 0, width_);
			try {
				pg.grabPixels();
			} catch (InterruptedException e) {}
			this.pixels_ = pixels;
		}
		/**
		 * コンストラクタ。
		 * @param width 作成したいイメージの幅(ドット単位)
		 * @param height 作成したいイメージの高さ(ドット単位)
		 * @param c イメージを作成するコンポーネント。
		 */
		public RGBMemoryImage(int width, int height) {
			this.width_ = width;
			this.height_ = height;
			pixels_ = new int[width * height];
			for (int i = 0; i < width * height; i++) { pixels_[i] = 255 << 24;}
		}

		/**
		 * コンストラクタ。
		 * 指定したイメージの内容で初期化します。
		 * @param image 初期状態のイメージ
		 * @param width 作成したいイメージの幅(ドット単位)
		 * @param height 作成したいイメージの高さ(ドット単位)
		 * @param c イメージを作成するコンポーネント。
		 */
		public RGBMemoryImage(Image image,int width,int height) {
			this.width_ = width;
			this.height_ = height;
			createPixels(image);
		}

		/**
		 * コンストラクタ。
		 * 指定したイメージの内容で初期化します。
		 * @param image 初期状態のイメージ
		 * @param c イメージを作成するコンポーネント。
		 */
		public RGBMemoryImage(Image image) {
			while(width_ <= 0 || height_ <= 0) {
				width_ = image.getWidth(null);
				height_ = image.getHeight(null);
				try {
					Thread.sleep(100);
				} catch(InterruptedException exception) {}
			}
			createPixels(image);
		}

		/**
		 * このイメージの幅を取得する。
		 */
		public final int getWidth() { return width_; }

		/**
		 * このイメージの高さを取得する。
		 */
		public final int getHeight() { return height_; }

		/**
		 * イメージにアクセスするためのint型配列を取得する。
		 */
		public int[] getPixels() {
			return pixels_;
		}
		/**
		 * imageオブジェクトを取得する。
		 */
		public Image getImage() {
			if (image_ == null) { createImage(); }
			return image_;
		}

		/**
		 * 配列に書き込んだ内容をイメージに反映させる。
		 */
		public void flush() {
			if (image_ == null) { createImage(); }
			memoryImageSource_.newPixels();
			//img.flush();
		}
	}
}