import java.lang.*; import java.util.*; import java.awt.*; import java.awt.event.*; import java.applet.*; /** * N体問題アプレット
*
* 与えられた物体間に万有引力が働いているものとして * 物体の運動を計算します。 * * @author M. Sugiura, Copyright (C) 1999 * @version 0.10 */ public class StarCluster extends Applet { /** * デバッグモード(今の所引数パースの結果を stderr に出力するだけ) */ public static int debug = 0; /** * 再描画の間隔(ミリ秒単位) */ public static int REFLESH_RATE = 50; /** * 軌道計算の時間間隔(ミリ秒単位) */ public static int DELAY_TIME = 10; /** * 1ミリ秒当りの軌道計算の反復数(非常に大雑把(^^;) */ public static int SAMPLE_RATE = 5; /** * 画面境界で反射するかどうか */ public static boolean REFLECT_BOUNDARY = false; /** * コンパネを表示するかどうか */ public static boolean SHOW_CTRLPANEL = false; /** * 重心を常に画面の中央に持ってくるかどうか */ public static boolean COM_TO_CENTER = true; /** * ニュートン重力定数(単位は適当(^^;) */ public static double G = 0.0005; /** * 空間と星の間の摩擦係数(減速用(^^;) */ public static double Mu = 0.0; /** * アプレットの幅 */ public static int width; /** * アプレットの高さ */ public static int height; /** * アプレットの背景色 */ public static Color bgcolor = Color.black; /** * 星のデータのリスト(スレッド) * * @see StarList */ static StarList stars; /** * 描画パネル(スレッド) * * @see StarList */ static ViewPanel vp; /** * コントロールパネル * * @see ControlPanel */ static ControlPanel cp; /** * アプレット情報を返す * * @return アプレット情報を含んだ文字列 */ public String getAppletInfo() { return "Applet StarCluster: made by M. Sugiura," + " 1999, All rights reserved."; } /** * アプレットが理解できるパラメータについての情報を返す * * @return このアプレットが探すパラメータについての情報を含む配列 */ public String[][] getParameterInfo() { return new String[][] { {"debug","0-","デバッグモード(1以上で何らかの出力あり)"}, {"reflesh_rate","1-1000","画面の再描画時間(ms)"}, {"delay_time","1-","軌道計算の時間間隔(ms)"}, {"sample_rate","1-100","1ミリ秒当りの計算の反復数"}, {"bgcolor","#RRGGBB","背景色"}, {"rotate_x","double","視点の x 軸回りの回転角"}, {"rotate_z","double","視点の z 軸回りの回転角"}, {"reflect_boundary","boolean","境界で反射するかどうか"}, {"show_ctrlpanel","boolean","コンパネを表示するかどうか"}, {"com_to_center","boolean","重心を中心にするかどうか"}, {"G","double","重力の強さ"}, {"Mu","double","摩擦係数"}, {"star_num","2-","星の数"}, {"star_?","opt1=val1;opt2=val2;..","星のプロパティの並び"}, {" c","#RRGGBB","星の色"}, {" m","double","星の質量"}, {" r","double","星の半径"}, {" p","double,double,double","星の初期座標"}, {" v","double,double,double","星の初期速度"} }; } /** * アプレットの初期化を行う(引数の処理etc.) */ public void init() { width = getSize().width; height = getSize().height; try { parseParameters(); } catch(InvalidParameterException e) { System.exit(1); } // 描画パネルの構築&アプレットに追加 setLayout(new BorderLayout()); vp = new ViewPanel(stars,bgcolor); add(vp,BorderLayout.CENTER); if (SHOW_CTRLPANEL) { // コンパネの追加 cp = new ControlPanel(); add(cp,BorderLayout.SOUTH); validate(); // set size of cp. height -= cp.getSize().height; vp.setSize(width,height); } } /** * アプレットの停止時に呼ばれる(描画&軌道計算スレッドを停止) */ public void stop() { if (vp != null && vp.isActive()) vp.stop(); } void parseParameters() throws InvalidParameterException { double rx = 0.0, rz = 0.0; try { // 背景色 String param = getParameter("bgcolor"); if (param != null && param.charAt(0) == '#') { try { bgcolor = Color.decode(param); } catch (NumberFormatException e) { throw new InvalidParameterException("bgcolor",param); } } // 視点の x 軸回りの回転角 if ((param=getParameter("rotate_x")) != null) { try { rx = Math.PI*Double.valueOf(param).doubleValue()/180.0; } catch (NumberFormatException e) { throw new InvalidParameterException("rotate_x",param); } } // 視点の z 軸回りの回転角 if ((param=getParameter("rotate_z")) != null) { try { rz = - Math.PI*Double.valueOf(param).doubleValue()/180.0; } catch (NumberFormatException e) { throw new InvalidParameterException("rotate_z",param); } } // 再描画の間隔 if ((param=getParameter("reflesh_rate")) != null) { try { int rr = Integer.parseInt(param,10); if (rr > 0 && rr <= 1000) REFLESH_RATE = rr; } catch (NumberFormatException e) { throw new InvalidParameterException("reflesh_rate",param); } } // 軌道計算の時間間隔 if ((param=getParameter("delay_time")) != null) { try { int dt = Integer.parseInt(param,10); if (dt > 0) DELAY_TIME = dt; } catch (NumberFormatException e) { throw new InvalidParameterException("delay_time",param); } } // 1ミリ秒当りの計算の反復数 if ((param=getParameter("sample_rate")) != null) { try { int sr = Integer.parseInt(param,10); if (sr > 0 && sr <= 100) SAMPLE_RATE = sr; } catch (NumberFormatException e) { throw new InvalidParameterException("sample_rate",param); } } // デバッグフラグ if ((param=getParameter("debug")) != null) { try { debug = Integer.parseInt(param); if (debug < 0) debug = -debug; } catch (NumberFormatException e) { throw new InvalidParameterException("debug",param); } } // 境界で反射するかどうか if ((param=getParameter("reflect_boundary")) != null) { REFLECT_BOUNDARY = Boolean.valueOf(param).booleanValue(); } // コンパネを表示するかどうか if ((param=getParameter("show_ctrlpanel")) != null) { SHOW_CTRLPANEL = Boolean.valueOf(param).booleanValue(); } // コンパネを表示するかどうか if ((param=getParameter("com_to_center")) != null) { COM_TO_CENTER = Boolean.valueOf(param).booleanValue(); } // 重力定数 if ((param=getParameter("G")) != null) { try { G = Double.valueOf(param).doubleValue(); } catch (NumberFormatException e) { throw new InvalidParameterException("G",param); } } // 摩擦係数 if ((param=getParameter("Mu")) != null) { try { Mu = Double.valueOf(param).doubleValue()*1.0e-6; } catch (NumberFormatException e) { throw new InvalidParameterException("Mu",param); } } // 星の数(なければ例外を送出) int num = Integer.parseInt(getParameter("star_num"),10); // 星の数は2以上 if (num < 2) { showMessage( "星の数が少なすぎます。2以上の整数を指定して下さい。" ); throw new InvalidParameterException( "star_num", String.valueOf(num) ); } // 星のデータのリスト stars = new StarList(num); // (運動が画面内に収まる程度の)デフォルトのスピード double vinit = Math.sqrt(4.0*G/width); // 以下星のデータのパース for (int i=0; i タグの解釈で例外が発生しました。\n" + e); throw e; } } public static void changeState(int state) { if (vp == null) return; switch (state) { case 0: // start threads. if (!vp.isActive()) vp.start(); break; case 1: // stop threads. if (vp.isActive()) vp.stop(); break; case 2: // reset threads. vp.reset(); } if (cp != null) cp.changeState(state); } public void showMessage(String msg) { (new MessageBox(getFrame(),msg,"Applet StarCluster Error:")).show(); } Frame getFrame() { Container p = (Container)this; while ((p = p.getParent()) != null) { if (p instanceof Frame) break; } return (Frame)p; } } class MessageBox extends Dialog implements ActionListener { MessageBox(Frame parent, String msg, String title) { super(parent,title,false); StringTokenizer msgtoken = new StringTokenizer(msg,"\n"); int msgnum = msgtoken.countTokens(); setLayout(new GridLayout(msgnum+1,1,2,2)); while (msgtoken.hasMoreTokens()) { Label lbl = new Label(msgtoken.nextToken(),Label.LEFT); lbl.setSize(400,20); add(lbl); } Button btn = new Button("OK"); btn.addActionListener(this); btn.setSize(400,40); add(btn); setSize(400,20*(msgnum+2)); } public void actionPerformed(ActionEvent e) { setVisible(false); } } /** * 描画パネル(スレッド) */ class ViewPanel extends Panel implements Runnable, MouseListener { /** * 自分自身(のスレッド)への参照 */ Thread view; /** * 星のデータのリストへの参照 * * @see StarList */ StarList stars; /** * @param stars 星のデータのリストへの参照 * @param bgcolor 背景色 */ ViewPanel(StarList stars, Color bgcolor) { this.stars = stars; setBackground(bgcolor); addMouseListener(this); } /** * 描画時に呼ばれる */ public void paint(Graphics g) { // 面倒(^^;なので背景色で全部塗りつぶす。 // 画面がちらつくのはこのせいなんだけど… g.clearRect(0,0,StarCluster.width,StarCluster.height); stars.paint(g); // 星を描画 } /** * アプレットの再描画の要求を処理する(paint() を呼ぶだけ) */ public void update(Graphics g) { paint(g); } /** * 描画スレッドの本体(REFLESH_RATE の間隔で repaint() を呼ぶ) * * @see StarCluster#REFLESH_RATE * @see java.awt.Component#repaint(java.awt.Graphics) */ public void run() { while (view != null) { try { view.sleep(StarCluster.REFLESH_RATE); } catch (InterruptedException e) { break; } repaint(); } } /** * 描画及び星の運動のスレッドを開始 * * @see StarList#start() */ public void start() { if (!stars.isActive()) stars.start(); view = new Thread(this); view.start(); } /** * 描画及び星の運動のスレッドを停止 * * @see StarList#stop() */ public void stop() { view = null; if (stars.isActive()) stars.stop(); } /** * 描画及び星の運動のスレッドを停止&再初期化 * * @see StarList#stop() * @see StarList#reset() */ public void reset() { if (isActive()) stop(); stars.reset(); repaint(); } /** * 描画スレッドがアクティブかどうかを返す */ public boolean isActive() { return view != null; } public void mouseClicked(MouseEvent e) { e.consume(); switch (e.getClickCount()) { case 0: // not occur!! break; case 1: // single click // toggle motion StarCluster.changeState(isActive()?1:0); break; default: // double or more clicks // positions reset StarCluster.changeState(2); break; } } public void mouseEntered(MouseEvent e) { } public void mouseExited(MouseEvent e) { } public void mousePressed(MouseEvent e) { } public void mouseReleased(MouseEvent e) { } } /** * コントロールパネル */ class ControlPanel extends Panel implements ActionListener { Button btn1, btn2; /** * @param vp 描画パネルへの参照 */ ControlPanel() { setLayout(new FlowLayout()); setBackground(Color.lightGray); btn1 = new Button("Start"); add(btn1); btn1.addActionListener(this); btn1.setActionCommand("change_state"); btn1.setBackground(Color.lightGray); btn2 = new Button("Reset"); add(btn2); btn2.addActionListener(this); btn2.setActionCommand("reset_state"); btn2.setBackground(Color.lightGray); } public void actionPerformed(ActionEvent e) { String cmd = e.getActionCommand(); if (cmd.equals("change_state")) { StarCluster.changeState(btn1.getLabel().equals("Start")?0:1); } else if (cmd.equals("reset_state")) { StarCluster.changeState(2); } } public void changeState(int state) { btn1.setLabel((state!=0)?"Start":"Stop"); } } /** * 3次元ベクトルを表す
* ベクトル演算メソッドは全て自分自身への参照を返す。
* (ショボイ高速化のため(^^; <- 随分早くなったけど(^^;) */ final class Vec { /** * ベクトルの x 成分 */ private double x; /** * ベクトルの y 成分 */ private double y; /** * ベクトルの z 成分 */ private double z; /** * 各成分を 0.0 で初期化する */ Vec() { set(0.0,0.0,0.0); } /** * 各成分を引数で初期化する */ Vec(double x, double y, double z) { set(x,y,z); } /** * 各成分を引数のベクトルで初期化する */ Vec(Vec a) { set(a.x,a.y,a.z); } /** * 各成分を引数の文字列("x,y,z")で初期化する * * @param "x,y,z" 形式の文字列 */ Vec(String s) { set(0.0,0.0,0.0); StringTokenizer st = new StringTokenizer(s," ,"); if (st.hasMoreTokens()) x = Double.valueOf(st.nextToken()).doubleValue(); if (st.hasMoreTokens()) y = Double.valueOf(st.nextToken()).doubleValue(); if (st.hasMoreTokens()) z = Double.valueOf(st.nextToken()).doubleValue(); } /** * x 成分を返す * * @return x 成分 */ public double x() {return x;} /** * y 成分を返す * * @return y 成分 */ public double y() {return y;} /** * z 成分を返す * * @return z 成分 */ public double z() {return z;} /** * 各成分を x, y, z に置き換える */ public Vec set(double x, double y, double z) { this.x = x; this.y = y; this.z = z; return this; } /** * 各成分を v.x, v.y, v.z に置き換える */ public Vec set(Vec v) { return set(v.x,v.y,v.z); } /** * 渡されたベクトルとの和を取る */ public Vec add(Vec a) { x += a.x; y += a.y; z += a.z; return this; } /** * 渡されたベクトルとの差を取る */ public Vec subtract(Vec a) { x -= a.x; y -= a.y; z -= a.z; return this; } /** * 渡された実数とのスカラー倍を取る */ public Vec multiply(double a) { x *= a; y *= a; z *= a; return this; } /** * 渡された実数(a)の逆数(1/a)とのスカラー倍を取る * (this.multiply(1.0/a) と同じではない (誤差が…)) */ public Vec devide(double a) { x /= a; y /= a; z /= a; return this; } /** * ベクトルの長さ (= sqrt(x*x+y*y+z*z)) を返す */ public double length() { return Math.sqrt(x*x+y*y+z*z); } /** * 与えられた角度分だけ x, z 軸に対して回転したベクトルを返す * * @param rx x 軸方向の回転角(rad.) * @param rz z 軸方向の回転角(rad.) */ public Vec rotate(double rx, double rz) { double nx = Math.cos(rz)*x - Math.sin(rz)*Math.cos(rx)*y + Math.sin(rz)*Math.sin(rx)*z, ny = Math.sin(rz)*x + Math.cos(rz)*Math.cos(rx)*y - Math.cos(rz)*Math.sin(rx)*z, nz = Math.sin(rx)*y + Math.cos(rx)*z; x = nx; y = ny; z = nz; return this; } /** * "{x,y,z}" 形式の文字列を返す */ public String toString() { return "{" + String.valueOf(x) + "," + String.valueOf(y) + "," + String.valueOf(z) + "}"; } } /** * 星のデータ * * @see StarList */ class Star implements Cloneable { /** * 星の色 */ public Color color; /** * 星の質量(単位は適当(^^;) */ public double mass; /** * 星の半径(単位はピクセル) */ public double radius; /** * 星の座標 */ public Vec coordinate; /** * 星の速度 */ public Vec velocity; /** * 星の次の時刻での座標を格納するテンポラリバッファ */ private Vec new_coord; /** * テンポラリ Vec オブジェクト */ private Vec vtmp; /** * @param color 色 * @param mass 質量 * @param r 半径 * @param pt 初期座標 * @param v 初期速度 */ Star(Color color, double mass, double r, Vec pt, Vec v) { this.color = color; this.mass = mass; this.radius = r; coordinate = new Vec(pt); new_coord = new Vec(pt); velocity = new Vec(v); vtmp = new Vec(); } /** * 複製メソッド */ public Star Clone() { Star ns; try { ns = (Star)super.clone(); } catch (CloneNotSupportedException e) { // not reach here. return null; } ns.coordinate = new Vec(coordinate); ns.new_coord = new Vec(new_coord); ns.velocity = new Vec(velocity); ns.vtmp = new Vec(); return ns; } /** * 引数で渡された星から受ける重力を計算する * * @param os 重力源 * @param f 結果を受け取る Vec オブジェクト */ public void forceFrom(Star os, Vec f) { f.set(0.0,0.0,0.0); if (this != os) { f.set(coordinate).subtract(os.coordinate); double r = f.length(); if (r > radius+os.radius) { vtmp.set(velocity).multiply(StarCluster.Mu); f.multiply(StarCluster.G*mass*os.mass) .devide(r*r*r).add(vtmp); } else { f.set(0.0,0.0,0.0); } } } /** * 次の座標と速度を計算 * * @param f 力 */ public synchronized void calc(Vec f) { // calculating next point. new_coord.set(coordinate) .add(vtmp.set(velocity).devide(StarCluster.SAMPLE_RATE)); velocity.subtract( vtmp.set(f).devide(mass).devide(StarCluster.SAMPLE_RATE) ); } /** * 次の座標への移動 */ public synchronized void step() { coordinate.set(new_coord); } /** * 星の z 座標の大小を比較 * * @param s 比較対象の星 * @return (this.z > s.z) */ public boolean largerThan(Star s) { return (this.coordinate.z()>s.coordinate.z()); } /** * 星のプロパティのダンプ * * @return "color = *, mass = *, radius = *, position = *, velocity = *" */ public String toString() { return "color = " + color.toString() + ", mass = " + String.valueOf(mass) + ", radius = " + String.valueOf(radius) + ",\n position = " + coordinate.toString() + ", velocity = " + velocity.toString(); } } /** * 星のデータのリスト
* このアプレットのマルチスレッド処理の同期は * 全てこのクラスの synchronized メソッドによって実現されている。
* * @see Star */ class StarList extends Vector implements Runnable { Thread stars; /** * 全質量 */ double totalmass = 0.0; /** * 構築時の状態のコピーへの参照 */ private Vector slinit; /** * 内力を一時的に格納するバッファ(num*(num-1)/2 次元配列) */ private Vec[] f; /** * テンポラリ Vec オブジェクト */ private Vec com, ftotal, vtmp; /** * @param num 要素数 */ StarList(int num) { super(num); slinit = new Vector(num); f = new Vec[num*(num-1)/2]; // f_ij = - f_ji for (int i=0; i * (反射のための処理も行っている) */ void translate() { Enumeration e; com.set(0.0,0.0,0.0); for (e = elements(); e.hasMoreElements();) { Star s = (Star)e.nextElement(); com.add(vtmp.set(s.coordinate).multiply(s.mass)); } com.devide(totalmass); for (e = elements(); e.hasMoreElements();) { Star s = (Star)e.nextElement(); if (StarCluster.COM_TO_CENTER) s.coordinate.subtract(com); if (StarCluster.REFLECT_BOUNDARY) { if ( s.coordinate.x() > StarCluster.width/2 && s.velocity.x() > 0.0 || s.coordinate.x() < -StarCluster.width/2 && s.velocity.x() < 0.0 ) { if (s.velocity.x() > 0.0) { s.coordinate.set( StarCluster.width - s.coordinate.x(), s.coordinate.y(), s.coordinate.z() ); } else { s.coordinate.set( -StarCluster.width - s.coordinate.x(), s.coordinate.y(), s.coordinate.z() ); } s.velocity.set( -s.velocity.x(), s.velocity.y(), s.velocity.z() ); } if ( s.coordinate.y() > StarCluster.height/2 && s.velocity.y() > 0.0 || s.coordinate.y() < -StarCluster.height/2 && s.velocity.y() < 0.0 ) { if (s.velocity.y() > 0.0) { s.coordinate.set( s.coordinate.x(), StarCluster.height - s.coordinate.y(), s.coordinate.z() ); } else { s.coordinate.set( s.coordinate.x(), -StarCluster.height - s.coordinate.y(), s.coordinate.z() ); } s.velocity.set( s.velocity.x(), -s.velocity.y(), s.velocity.z() ); } } } } /** * 各要素を z 軸の降順で(クイック)ソートする * * @param left ソート範囲の開始位置 * @param right ソート範囲の終了位置 */ void sort(int left, int right) { // K&R まんま(^^; if (left >= right) return; swap(left,(left+right)/2); int last = left; for (int i=left+1; i<=right; i++ ) { if (!((Star)elementAt(i)).largerThan((Star)elementAt(left))) swap(++last,i); } swap(left,last); sort(left,last-1); sort(last+1,right); } /** * i 番目と j 番目の要素を交換する */ void swap(int i, int j) { Object tmps = elementAt(i); setElementAt(elementAt(j),i); setElementAt(tmps,j); } /** * 星のデータをリストに追加 * * @param s 追加する星のデータ * @see Star */ public synchronized void addStar(Star s) { addElement(s); slinit.addElement(s.Clone()); totalmass += s.mass; } /** * データの再初期化 */ public synchronized void reset() { removeAllElements(); for (Enumeration e=slinit.elements(); e.hasMoreElements();) { addElement(((Star)e.nextElement()).Clone()); } } /** * 軌道計算を実行する */ public synchronized void step() { for (int t=StarCluster.DELAY_TIME*StarCluster.SAMPLE_RATE; t>0; t--) { // 内力の計算 (f_ij (i j) { ftotal.subtract(f[j*(2*num-j-1)/2+i-j-1]); } } ((Star)elementAt(i)).calc(ftotal); // 次の位置を求める } // わざわざ別のループにする必要があるのか??…ないかも(爆)。 for (Enumeration e=elements(); e.hasMoreElements();) { ((Star)e.nextElement()).step(); // 実際に次の位置へ移動 } // 境界で反射させる時 if (StarCluster.REFLECT_BOUNDARY) translate(); } } /** * 星を描画する * * @param g 描画対象のグラフィックス要素 * @see java.awt.Graphics */ public synchronized void paint(Graphics g) { // 重なりを考えて、描画の前に z 軸の小さい順に並べ替える sort(0,size()-1); // 境界での反射の効果の計算&画面中央に重心を持ってくる translate(); // 描画の原点を画面中心にずらす g.translate(StarCluster.width/2,StarCluster.height/2); Color oldcolor = g.getColor(); for (Enumeration e=elements(); e.hasMoreElements();) { Star s = (Star)e.nextElement(); // 星の描画 g.setColor(s.color); // 遠近感をだすために描画の半径を z 座標の値に従って変える double r = s.radius * ( 1.5 + 2.0*Math.atan(4.0*s.coordinate.z()/StarCluster.width) /Math.PI ); if( (int)(r*2) < 4 ){ // 円が潰れそうな時は代わりに点を描く g.fillRect( (int)s.coordinate.x(), (int)s.coordinate.y(), 1,1 ); } else { // 円の内部を塗りつぶして… g.fillOval( (int)(s.coordinate.x()-r), (int)(s.coordinate.y()-r), (int)r*2,(int)r*2 ); // 縁を描く。こうすると縁がキレイ(^^; g.drawOval( (int)(s.coordinate.x()-r), (int)(s.coordinate.y()-r), (int)r*2-1,(int)r*2-1 ); } } g.setColor(oldcolor); } /** * リスト中の各要素のプロパティを繋げた物を返す * * @return Star.toString() + "\n" + .. * @see Star#toString() */ public synchronized String enumStars() { StringBuffer ret = new StringBuffer(); for(Enumeration e=elements(); e.hasMoreElements();){ ret.append(((Star)e.nextElement()).toString()).append("\n"); } return ret.toString(); } } class InvalidParameterException extends Exception { String paramname; Object value; InvalidParameterException(String paramname, Object value) { super("パラメータ名: " + paramname + "\n値:" + value); this.paramname = paramname; this.value = value; } }