AI (o3-mini-high) が一発ノーミスで実現！〜球体内部にカラフルなボールが跳ね回る3Dシミュレーションの実装〜

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/82835/c00638b7-67e4-829b-a1af-a54ca8edfcf8.png)

### はじめに

今回、ChatGPT o3-mini-high を用いて、下記のような詳細なプロンプトに基づき、  
**球体の内部で 100 個のカラフルなボールが跳ね回る 3D シミュレーション**を実装しました。  
シミュレーションは、p5.js の WebGL モードを利用しており、ブラウザ全画面で 3D 表現が可能です。  
また、生成されたコードはすべての要件を満たしており、エラーも一切ありません。  
[editor.p5js.org](https://editor.p5js.org/) にコードを貼り付ければ、すぐに実行できます！

---

### シミュレーションの特徴

- **キャンバスとカメラ設定**  
  - `createCanvas(windowWidth, windowHeight, WEBGL)` を使用し、ブラウザ全画面での 3D 表示が実現されています。  
  - `orbitControl()` を利用することで、マウス操作によるカメラ操作（回転・ズーム）が可能です。

- **球体のコンテナ**  
  - 半径 300 の球体コンテナは、内部が見やすいように半透明のワイヤーフレームで描画。  
  - ゆっくりと回転するアニメーションにより、内部の動きを把握しやすくしています。

- **ボールの生成と挙動**  
  - 100 個のボールは、ランダムな初期位置、速度、色、サイズ（半径 5〜15）で生成。  
  - 衝突検出では、球体の内壁との接触時に法線を利用した鏡面反射計算を行い、ボールが外に出ないように制御しています。

- **ボールの軌跡（トレイル）**  
  - 各ボールが移動した軌跡は、古い部分がフェードアウトするように描画され、動きの歴史が視覚的に表現されます。

- **ユーザーインターフェース**  
  - オプションとして、コンテナの回転速度を調整できるスライダーを実装。直感的にシーンの動きを変更可能です。

---

### 完成コード

以下のコードは、ChatGPT o3-mini-high によって一発ノーミスで生成されたものです。  
各部分に詳細なコメントを入れており、学習目的にも最適です。

```js
// --- グローバル変数 ---
let balls = [];               // 100 個のボールを格納する配列
const numBalls = 100;         // ボールの個数
const containerRadius = 300;  // 球体コンテナの半径
let containerRotation = 0;    // 球体コンテナの回転角（アニメーション用）
let rotationSpeedSlider;      // オプション：コンテナ回転速度調整用スライダー

// --- setup() ---
// キャンバス作成、ボールの初期化、UI の生成など
function setup() {
  // ブラウザ全画面の WEBGL キャンバスを作成
  createCanvas(windowWidth, windowHeight, WEBGL);
  
  // 背景色やレンダリング設定
  colorMode(RGB, 255, 255, 255, 255);
  
  // 100 個のボールを生成（各ボールはコンストラクタ内でランダムな初期位置・速度・大きさ・色を持つ）
  for (let i = 0; i < numBalls; i++) {
    balls.push(new Ball());
  }
  
  // オプション：コンテナの回転速度を調整できるスライダーを作成
  // 範囲は 0 ~ 0.05、初期値 0.01、刻み幅 0.001
  rotationSpeedSlider = createSlider(0, 0.05, 0.01, 0.001);
  rotationSpeedSlider.position(20, 20);
  rotationSpeedSlider.style('width', '200px');
}

// --- windowResized() ---
// ウィンドウサイズ変更時にキャンバスサイズを更新
function windowResized() {
  resizeCanvas(windowWidth, windowHeight);
}

// --- draw() ---
// 毎フレームの描画・更新処理
function draw() {
  // 背景色を設定（暗い背景）
  background(20);
  
  // ユーザー操作可能なカメラ操作（ドラッグやホイールで視点操作）
  orbitControl();
  
  // 照明設定
  // シーン全体をやわらかく照らすアンビエントライト
  ambientLight(50);
  // 手前上方からのポイントライト
  pointLight(255, 255, 255, 0, -containerRadius * 2, 500);
  
  // スライダーで指定された回転速度でコンテナをゆっくり回転
  let rotSpeed = rotationSpeedSlider.value();
  containerRotation += rotSpeed;
  
  // --- 球体コンテナの描画 ---
  push();
    // 回転アニメーション（Y軸周り）
    rotateY(containerRotation);
    // 内部が見やすいよう、半透明のワイヤーフレームで描画
    noFill();
    stroke(200, 200, 255, 150);
    strokeWeight(1);
    // sphere() の第2・第3引数でディテール数を指定（パフォーマンス調整用）
    sphere(containerRadius, 24, 16);
  pop();
  
  // --- 各ボールの更新＆描画 ---
  for (let ball of balls) {
    ball.update();
    ball.display();
  }
}

// ======================================================================
// ボールを表すクラス
// 各ボールは位置、速度、半径、色、そして移動軌跡（トレイル）を持ちます。
// ======================================================================
class Ball {
  constructor() {
    // ボールの大きさ（半径）は 5～15 のランダム値
    this.r = random(5, 15);
    // ボールの色：明るめのランダムな RGB 値
    this.col = color(random(50, 255), random(50, 255), random(50, 255));
    
    // ボールの初期位置は「球体コンテナ内」に配置
    // コンテナの内側に収まるよう、最大距離は containerRadius - this.r
    let maxDist = containerRadius - this.r;
    // ランダムな方向を取得し、0～maxDist のランダムな長さを掛ける
    let pos = p5.Vector.random3D();
    pos.mult(random(0, maxDist));
    this.pos = pos;
    
    // ボールの初期速度はランダムな 3D ベクトル
    // 速度の大きさは 1～3 の範囲（シーンの動き具合を調整可能）
    let vel = p5.Vector.random3D();
    vel.mult(random(1, 3));
    this.vel = vel;
    
    // トレイル（軌跡）として過去の位置を記録する配列
    this.trail = [];
    // トレイルの最大長（過去の位置の数）を設定
    this.maxTrailLength = 30;
  }
  
  // --- update() ---  
  // 位置の更新、コンテナ内での衝突判定＆反射、軌跡の記録を行います。
  update() {
    // 位置に速度を加算して更新
    this.pos.add(this.vel);
    
    // --- 衝突判定＆反射処理 ---
    // 球体コンテナの内壁との衝突判定：
    // ボールの中心から原点までの距離 d にボールの半径 this.r を加えた値が
    // コンテナの半径 containerRadius より大きくなれば衝突と判定
    let d = this.pos.mag();
    if (d + this.r > containerRadius) {
      // 衝突点での「法線」は原点からボールの位置への単位ベクトル
      let n = this.pos.copy().normalize();
      // 速度ベクトルと法線との内積を求める
      let vDotN = this.vel.dot(n);
      // ミラー反射の公式： v' = v - 2*(v・n)*n
      // （ボールが外向きに動いている場合 vDotN > 0 となります）
      if (vDotN > 0) {
        this.vel.sub(p5.Vector.mult(n, 2 * vDotN));
      }
      // 衝突後、ボールがコンテナ外に出ないよう、ボールの位置を内壁上に補正
      // 補正後の位置は「法線ベクトル × (containerRadius - this.r)」
      this.pos = n.mult(containerRadius - this.r);
    }
    
    // --- トレイルの記録 ---
    // 現在の位置のコピーをトレイル配列に追加
    this.trail.push(this.pos.copy());
    // トレイルの長さが設定した上限を超えたら、古い位置を削除
    if (this.trail.length > this.maxTrailLength) {
      this.trail.shift();
    }
  }
  
  // --- display() ---
  // ボール自体と、過去の軌跡（トレイル）を描画します。
  display() {
    // --- トレイルの描画 ---
    // 各トレイルの線分は、ボールの色で描画しつつ、
    // 古い部分ほど透明度を下げることでフェードアウトさせます。
    noFill();
    // トレイルは、記録した位置を順に結んだ線分として描画
    for (let i = 0; i < this.trail.length - 1; i++) {
      let pos1 = this.trail[i];
      let pos2 = this.trail[i + 1];
      // インデックスが古いほど alpha 値が低くなるように補間
      let alpha = map(i, 0, this.trail.length - 1, 50, 255);
      stroke(red(this.col), green(this.col), blue(this.col), alpha);
      strokeWeight(2);
      line(pos1.x, pos1.y, pos1.z, pos2.x, pos2.y, pos2.z);
    }
    
    // --- ボール自体の描画 ---
    push();
      translate(this.pos.x, this.pos.y, this.pos.z);
      noStroke();
      fill(this.col);
      // ボールは低ディテールの球体として描画（パフォーマンス配慮）
      sphere(this.r, 8, 6);
    pop();
  }
}
```

### 実行方法

1. [editor.p5js.org](https://editor.p5js.org/) にアクセスします。  
2. 新規スケッチを作成し、上記コードをエディタに貼り付けます。  
3. 「再生」ボタンをクリックすると、ブラウザ全画面でシミュレーションが実行されます。  
4. マウスのドラッグやホイール操作でシーンを自由に回転・ズームでき、詳細な動きを確認できます。

---

### 使用したプロンプト

以下は、今回のシミュレーション作成に用いた詳細なプロンプトの一部です。  
（実際のプロンプトは上記の要件に沿っており、p5.js のキャンバス設定、球体コンテナ、ボール生成・衝突処理、トレイル描画、パフォーマンス最適化など、各機能が明確に記述されています。）

```txt
以下の要件を含め、**p5.js（WebGL モード）**を使用して球体の内部で 100 個のカラフルなボールが跳ね回る 3D シミュレーションを作成してください

## 1. キャンバスとカメラ設定
- `createCanvas(windowWidth, windowHeight, WEBGL)` を使用し、ブラウザ全画面で 3D 表示できるようにしてください。  
- シーンがわかりやすいよう、視点の位置やカメラの動きを適宜調整してください。

## 2. 球体のコンテナ
- 球体の半径は 300 程度に設定し、ボールが視認しやすいようにしてください。  
- 球体はゆっくりと回転するようにアニメーションさせ、内部を把握できるようにしてください。  
- 球体の描画はワイヤーフレームや半透明など、内部が見やすい方法を検討してください。

## 3. ボールの生成と挙動
- **ボールの個数**: 100 個  
- **色・大きさ**: ランダムな色で、半径 5〜15 程度の大きさに設定してください。  
- 初期位置と速度（ベクトル）をランダムに与え、球体内部を動き回るようにしてください。  
- 衝突検出により、ボールが球体の内壁に衝突した際には**反射計算**を行い、球体の外には出ないようにしてください。  
  - 反射処理は単純な鏡面反射（法線方向で速度ベクトルを反転）で構いません.

## 4. ボールの軌跡（トレイル）
- 各ボールが移動した後に、**軌道を示すトレイル**を描画してください。  
- トレイルはフレームごとに徐々にフェードアウトし、最新の軌跡が色濃く残るようにしてください（アルファ値やレイヤー描画を利用）。

## 5. 描画パフォーマンスと見栄え
- 1 フレームあたりの描画が重くなりすぎないよう、**最適化**を検討してください。  
  - 不要なオブジェクト生成を避ける、背景クリアの方法を工夫するなど。  
- シーンの見栄えがよくなるよう、**背景色や照明、マテリアル**などを適切に設定してください。  
  - `ambientLight`, `pointLight` などの使用を検討.

## 6. ユーザーへの操作ガイド（任意）
- ユーザーが**ボタンやスライダー**などで球体の回転速度やボールの色・大きさの範囲を変更できる**インタラクティブ UI**を加えても構いません.

## 7. コードの可読性とコメント
- 変数名や関数名はわかりやすくし、主要な部分にはコメントを適宜入れてください。  
- 衝突判定や反射処理がどのように行われているかを明確にし、学習目的で読んだ際に理解しやすいように記述してください.

```

---

### まとめ
今回のシミュレーションは、ChatGPT o3-mini-high の高度なコード生成能力を実感できる一例です。
p5.js（WebGL）を活用したリアルタイムの 3D シミュレーションを、シンプルな設定で実現できる点や、
AI が即座にミスなくコードを生成できるクオリティの高さをぜひ体験してください。