import java.applet.Applet; import java.awt.*; import java.awt.event.*; import java.util.*; /* */ public class Hello3D_Sphere4 extends Applet implements MouseMotionListener, MouseListener, Runnable { private static final long serialVersionUID = 6701733854722605791L; ArrayList earth; // ベースとなる球面 ArrayList komas; // 球面上に駒を置いてみる Point center; // アプレットの中心座標 Point mousePosition; // マウス位置 double scale; // モデル描画時のスケール double phi; // x軸周りの回転角 double theta; // y軸周りの回転角 Image bufferImage; // ダブルバッファリング用のイメージ Dimension appletSize; // アプレットサイズ private boolean main_loop; //ループの継続条件 public void init() { // イベントリスナの登録 addMouseMotionListener(this); addMouseListener(this); // アプレットサイズの取得 appletSize = getSize(); // アプレットの中心座標の取得 center = new Point(appletSize.width / 2, appletSize.height / 2); // マウス位置の初期化 mousePosition = new Point(0, 0); // 描画スケールの設定 scale = appletSize.width * 0.8 / 2; // 回転角の初期化 phi = 0.0; theta = 0.0; // モデルデータの設定 setModelData(); // 頂点のスクリーン座標の設定 setScreenPosition(); main_loop = true; //自分を渡してThreadクラスを作成しスレッド起動 new Thread(this).start(); } public void paint(Graphics g) { // ダブルバッファリング用のイメージを作成 if(bufferImage == null) { bufferImage = createImage(appletSize.width, appletSize.height); } // バッファにモデルを描画 drawModel(bufferImage.getGraphics()); // バッファイメージをアプレットに描画 g.drawImage(bufferImage, 0, 0, this); } // 描画更新時に背景の塗りつぶし処理を行わないためのオーバーライド public void update(Graphics g) { paint(g); } public void mouseMoved(MouseEvent e) {} public void mouseClicked(MouseEvent e) { } public void mouseEntered(MouseEvent e) { } public void mouseExited(MouseEvent e) { } public void mouseReleased(MouseEvent e) { } public void mouseDragged(MouseEvent e) { // 回転角の更新 theta += (e.getX() - mousePosition.x) * 0.01; phi += (e.getY() - mousePosition.y) * 0.01; // x軸周りの回転角に上限を設定 phi = Math.min(phi, Math.PI/2); phi = Math.max(phi, -Math.PI/2); // マウス位置の更新 mousePosition.setLocation(e.getX(), e.getY()); // 頂点のスクリーン座標の更新 setScreenPosition(); // 描画更新 repaint(); } public void mousePressed(MouseEvent e) { // マウス位置の更新 mousePosition.setLocation(e.getX(), e.getY()); } // モデルデータの設定 public void setModelData() { earth = new ArrayList(); komas = new ArrayList(); // ベース球面モデルの作成 earth.add( new VertexR( 0, Math.PI ) ); // 北極 for(double z = -1.0+0.25; z < 1.0; z += 0.25 ){ for(int i=0; i < 12; i++ ){ earth.add( new VertexR( i * 2 * Math.PI / 12 , Math.acos( z ) ) ); } } earth.add( new VertexR( 0, 0 ) ); // 南極 // 駒の初期化 for(int i=0; i < 1000; i++ ){ // 1000個くらい出してみますか VertexK koma = sphereRndKoma(); koma.vth = 0.05 ; // * Math.random(); koma.vph = 0.03 ; // * Math.random(); komas.add( koma ); } } // 駒をランダムな位置に作成する private VertexK sphereRndKoma() { double z = Math.random() * 2 -1; double rs = Math.random() * Math.PI * 2; return new VertexK( rs, Math.acos(z) ); } // 頂点のスクリーン座標を更新する private void setScreenPosition() { for(int i = 0; i < earth.size(); i++) { VertexR v = (VertexR)earth.get(i); v.rotate(theta, phi); // 回転後の座標値の算出 v.project(center, scale); // スクリーン座標の算出 } for(int i = 0; i < komas.size(); i++) { VertexK koma = (VertexK)komas.get(i); koma.rotate(theta, phi); koma.project(center, scale); } } // モデルの描画 private void drawModel(Graphics g) { // 全体をクリア g.setColor(Color.BLACK); g.fillRect(0, 0, appletSize.width, appletSize.height); // ベース球面の描画 g.setColor(Color.GREEN); drawEarth(g); // 駒の描画 // g.setColor(Color.YELLOW); for(int i = 0; i < komas.size(); i++) { VertexK koma = (VertexK)komas.get(i); if( koma.rz >= 0.0 ){ // 手前と奥で色を変える g.setColor(Color.YELLOW); }else{ g.setColor(Color.BLUE); } g.fillOval(koma.screenX -2, koma.screenY -2, 4, 4); } } // ベース球面の描画 -- 頂点をうまい具合に線で結ぶ private void drawEarth(Graphics g) { VertexR v1, v2; int i; int base = 1; // 経線(横線)を描く // for( double z = -1.0+0.25; z < 1.0; z += 0.25 ){ for( int j= -3; j < 4; j++ ){ for(i=0; i < 12 -1; i++ ){ v1 = (VertexR)earth.get(base + i); v2 = (VertexR)earth.get(base + i + 1); // 1つ先の頂点と結ぶ g.drawLine( v1.screenX, v1.screenY, v2.screenX, v2.screenY ); } // 円の最後を閉じる v1 = (VertexR)earth.get(base + i); v2 = (VertexR)earth.get(base); g.drawLine( v1.screenX, v1.screenY, v2.screenX, v2.screenY ); base += (++i); } // 緯線(縦線)の北極回り base = 0; v1 = (VertexR)earth.get(base); base = 1; for(i=0; i < 12; i++ ){ v2 = (VertexR)earth.get(base + i); g.drawLine( v1.screenX, v1.screenY, v2.screenX, v2.screenY ); } // 緯線を描く base = 1; // for( double z = -1.0+0.25; z < 1.0-0.25; z += 0.25 ){ for( int j= -3; j < 3; j++ ){ for(i=0; i < 12; i++ ){ v1 = (VertexR)earth.get(base + i); v2 = (VertexR)earth.get(base + i + 12); // 12個先の頂点と結ぶ g.drawLine( v1.screenX, v1.screenY, v2.screenX, v2.screenY ); } base += i; } // 緯線(縦線)の南極回り v2 = (VertexR)earth.get( earth.size() - 1 ); for(i=0; i < 12; i++ ){ v1 = (VertexR)earth.get(base + i); g.drawLine( v1.screenX, v1.screenY, v2.screenX, v2.screenY ); } } public void destroy() { main_loop = false; //ループの終了 super.destroy(); } public void run() { while(main_loop){ // 駒を動かす moveKomas(); // 頂点のスクリーン座標の更新 setScreenPosition(); repaint(); // 再描画 try // 時間待ち { Thread.sleep(33); } catch( InterruptedException ex ) { ex.printStackTrace(); } } } // 駒を動かす private void moveKomas(){ for(int i = 0; i < komas.size(); i++) { VertexK koma = (VertexK)komas.get(i); koma.move(); } } } class VertexK extends VertexR { public double vth, vph; public VertexK(double th, double ph) { super(th, ph); } public VertexK(double x,double y,double z) { super(x,y,z); } public void move(){ th += vth; ph += vph; RtoD(); } } /** * 頂点クラス */ class VertexR { public double th, ph; // モデルの頂点角度座標 public double x, y, z; // モデルの頂点座標 public double rx, ry, rz; // 回転させた後の座標 public int screenX, screenY; // スクリーン上の座標 // コンストラクター、角度から public VertexR(double th, double ph) { this.th = th; this.ph = ph; RtoD(); } // 角度 -> XYZ public void RtoD(){ this.x = Math.cos(this.th) * Math.sin(this.ph); this.y = Math.sin(this.th) * Math.sin(this.ph); this.z = Math.cos(this.ph); } // コンストラクター、XYZから public VertexR(double x,double y,double z) { this.x = x; this.y = y; this.z = z; } // 回転する public void rotate(double theta, double phi) { // 回転後の座標値の算出 this.rx = this.x * Math.cos(theta) + this.z * Math.sin(theta); this.ry = this.x * Math.sin(phi) * Math.sin(theta) + this.y * Math.cos(phi) - this.z * Math.sin(phi) * Math.cos(theta); this.rz = - this.x * Math.cos(phi) * Math.sin(theta) + this.y * Math.sin(phi) + this.z * Math.cos(phi) * Math.cos(theta); } /* -- 回転変換 X軸回りにφ回転 ○ Y軸回りにθ回転 = 合成 1 0 0 cosθ 0 sinθ cosθ 0 sinθ 0 cosφ -sinφ 0 1 0 sinφcosθ cosφ -sinφcosθ 0 sinφ cosφ -sinθ 0 cosθ -cosφsinθ sinφ cosφsinθ */ // スクリーン上に投影する public void project(Point center, double scale) { this.screenX = (int)(center.x + scale * this.rx ); this.screenY = (int)(center.y - scale * this.ry ); } }