0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

AI (o3-mini-high) で一発ノーミス!〜p5.js (WebGL) を使った球体内部に100個のカラフルなボールが跳ね回る3Dシミュレーションの実装〜

Posted at

はじめに

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


シミュレーションの特徴

  • キャンバスとカメラ設定

    • createCanvas(windowWidth, windowHeight, WEBGL) を使用し、ブラウザ全画面での 3D 表示が実現されています。
    • orbitControl() を利用することで、マウス操作によるカメラ操作(回転・ズーム)が可能です。
  • 球体のコンテナ

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

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

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

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

完成コード

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

// --- グローバル変数 ---
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 にアクセスします。
  2. 新規スケッチを作成し、上記コードをエディタに貼り付けます。
  3. 「再生」ボタンをクリックすると、ブラウザ全画面でシミュレーションが実行されます。
  4. マウスのドラッグやホイール操作でシーンを自由に回転・ズームでき、詳細な動きを確認できます。

使用したプロンプト

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

以下の要件を含め、**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 が即座にミスなくコードを生成できるクオリティの高さをぜひ体験してください。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?