coriolisPerfect.pde
// 円柱型スペースコロニーのシミュレーション
float radius = 300; // 画面上での円の半径(ピクセル)
float realRadius = 6.0; // 実際のコロニーの半径(メートル)
float g = 9.8; // 地球の重力加速度(m/s²)
float omega; // コロニーの角速度(rad/s)
float timeScale = 0.5; // 時間のスケール(シミュレーション速度調整用)
float dt = 0.01; // 時間ステップ(秒)
// ボールのプロパティ
float ballX, ballY; // ボール位置
float ballVX, ballVY; // ボール速度
float ballRadius = 10; // ボールの半径(描画用)
float apparentGravity = 0; // 見かけ上の重力
ArrayList<PVector> trajectory = new ArrayList<PVector>(); // ボールの軌跡を記録
boolean ballLaunched = false; // ボールが発射されたかどうか
// 回転の視覚化のためのマーカー
float colonyRotation = 0; // コロニーの回転角度
ArrayList<PVector> markers = new ArrayList<PVector>(); // 回転マーカーの位置
void setup() {
size(700, 700);
background(0);
// 地球の重力と同じになる角速度を計算
omega = sqrt(g / realRadius)*1;
// ボールの初期位置をリセット
resetBall();
// 回転マーカーを追加
for (int i = 0; i < 8; i++) {
float angle = TWO_PI * i / 8;
markers.add(new PVector(cos(angle) * radius * 0.9, sin(angle) * radius * 0.9));
}
fill(255);
textSize(14);
}
void draw() {
// 背景を黒に
background(0);
// 座標系の原点をウィンドウの中央に移動
translate(width/2, height/2);
// 現在のコロニーの回転を更新
colonyRotation += omega * dt * timeScale * 5; // 視覚化のため少し速くしています
// スペースコロニーの輪郭を描画
noFill();
stroke(0, 255, 0);
ellipse(0, 0, radius*2, radius*2);
// 回転マーカーを描画
pushMatrix();
rotate(colonyRotation);
fill(100, 200, 255, 150);
for (PVector marker : markers) {
ellipse(marker.x, marker.y, 30, 30);
// マーカー間を結ぶ線
stroke(100, 200, 255, 100);
line(0, 0, marker.x, marker.y);
}
// 壁の質感を追加(オプション)
noFill();
stroke(100, 200, 255, 50);
for (int i = 0; i < 20; i++) {
ellipse(0, 0, radius * 2 * (0.9 + i * 0.005), radius * 2 * (0.9 + i * 0.005));
}
popMatrix();
// 物理シミュレーション
if (ballLaunched) {
// 複数回更新して精度を上げる
for (int i = 0; i < 5; i++) {
updateBallPhysics();
}
// ボールの軌跡を記録
trajectory.add(new PVector(ballX, ballY));
// 軌跡が長すぎる場合は古いポイントを削除
if (trajectory.size() > 2000) {
trajectory.remove(0);
}
}
// 軌跡を描画
stroke(255, 100, 100);
noFill();
beginShape();
for (PVector p : trajectory) {
vertex(p.x, p.y);
}
endShape();
// ボールを描画
fill(255, 200, 0);
noStroke();
ellipse(ballX, ballY, ballRadius*2, ballRadius*2);
// 見かけ上の重力の計算
updateApparentGravity();
// 情報テキスト表示
displayInfoText();
// 見かけ上の重力をバーで表示
displayGravityBar();
}
void updateBallPhysics() {
// スケール変換(画面座標 → 実座標)
float x = ballX * realRadius / radius;
float y = ballY * realRadius / radius;
float vx = ballVX * realRadius / radius;
float vy = ballVY * realRadius / radius;
// 遠心力加速度(見かけの重力)
float r = sqrt(x*x + y*y);
float ax_centrifugal = 0;
float ay_centrifugal = 0;
if (r > 0.01) { // ゼロ除算防止
ax_centrifugal = omega*omega * x;
ay_centrifugal = omega*omega * y;
}
// コリオリ力加速度
float ax_coriolis = -2 * omega * vy;
float ay_coriolis = 2 * omega * vx;
// 加速度の合計
float ax = ax_centrifugal + ax_coriolis;
float ay = ay_centrifugal + ay_coriolis;
// 速度と位置の更新
vx += ax * dt * timeScale;
vy += ay * dt * timeScale;
x += vx * dt * timeScale;
y += vy * dt * timeScale;
// 壁との衝突判定・反射
float distFromCenter = sqrt(x*x + y*y);
if (distFromCenter > realRadius - 0.1) { // ボールが壁に当たったら
// 法線方向ベクトル
float nx = x / distFromCenter;
float ny = y / distFromCenter;
// 速度の法線成分
float vn = vx * nx + vy * ny;
// 反射後の速度計算(反発係数0.8)
vx = vx - (1 + 0.99) * vn * nx;
vy = vy - (1 + 0.99) * vn * ny;
// 位置の調整(壁の内側に戻す)
float correctionFactor = (realRadius - 0.1) / distFromCenter;
x *= correctionFactor;
y *= correctionFactor;
}
// スケール変換(実座標 → 画面座標)
ballX = x * radius / realRadius;
ballY = y * radius / realRadius;
ballVX = vx * radius / realRadius;
ballVY = vy * radius / realRadius;
}
void updateApparentGravity() {
// ボールの実座標でのポジション
float x = ballX * realRadius / radius;
float y = ballY * realRadius / radius;
// 遠心力によるみかけの重力の大きさを計算
float r = sqrt(x*x + y*y);
apparentGravity = omega * omega * r;
}
void displayGravityBar() {
// 画面右側に重力バーを表示
float barX = width/2 - 120;
float barY = -height/2 + 70;
float barHeight = 20;
float barMaxWidth = 100;
// 地球の重力に対する相対値(1.0が地球と同じ)
float relativeGravity = apparentGravity / g;
// バーの枠を表示
stroke(200);
noFill();
rect(barX, barY, barMaxWidth, barHeight);
// バーを表示(相対重力に応じて色を変える)
if (relativeGravity < 0.8) {
fill(100, 200, 100); // 弱い重力は緑色
} else if (relativeGravity < 1.2) {
fill(200, 200, 100); // 地球に近い重力は黄色
} else {
fill(200, 100, 100); // 強い重力は赤色
}
// バーの長さは相対重力に比例(最大2G相当)
float barWidth = min(barMaxWidth, barMaxWidth * relativeGravity / 2.0);
rect(barX, barY, barWidth, barHeight);
}
void displayInfoText() {
fill(255);
textAlign(LEFT);
// 画面左上に情報表示
text("Rotate Speed: " + nf(2*PI/omega, 1, 2) + " sec", -width/2 + 20, -height/2 + 30);
text("Virtual Gravity: " + nf(apparentGravity, 1, 2) + " m/s²", -width/2 + 20, -height/2 + 50);
text("Earth's G: ratio " + nf(apparentGravity/g, 1, 2) + "G", -width/2 + 20, -height/2 + 70);
text("click shoot | R key reset", -width/2 + 20, -height/2 + 90);
}
void resetBall() {
// ボールを中心近くに配置
ballX = radius * 0.5; // 右半分に配置
ballY = 0;
// 初期速度をゼロに
ballVX = 0;
ballVY = 0;
// 軌跡をクリア
trajectory.clear();
// 発射フラグをリセット
ballLaunched = false;
// 見かけ上の重力を更新
updateApparentGravity();
}
void mousePressed() {
if (!ballLaunched) {
// ボールを発射
// マウス位置への初速度を設定
PVector direction = new PVector(mouseX - width/2 - ballX, mouseY - height/2 - ballY);
direction.normalize();
// 初速度の大きさ(画面スケールで100ピクセル/秒)
float speed = 200;
ballVX = direction.x * speed;
ballVY = direction.y * speed;
ballLaunched = true;
}
}
void keyPressed() {
if (key == 'r' || key == 'R') {
resetBall();
}
}