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 );
}
}